From 8e7dd7073fb3bced0c49c5e65416436359390197 Mon Sep 17 00:00:00 2001 From: Serdar Kuzucu Date: Wed, 2 Dec 2015 10:14:16 +0200 Subject: [PATCH 001/774] Add appendTo(Node parent) method to Element. appendTo method appends the element to a given parent element and returns itself, instead of the parent. See jQuery's appendTo method for further information. As an example, use the code below; element.appendTo(parentElement).attr('class', 'text-danger').text('Alert!!'); Instead of the code below; element.attr('class', 'text-danger').text('Alert!!'); parentElement.appendChild(element); --- src/main/java/org/jsoup/nodes/Element.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index da363a2c61..9c62a37269 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -290,6 +290,25 @@ public Element appendChild(Node child) { return this; } + /** + * Add this node as a child node to the given parent + * + * @param parent node in which this node will be appended + * @return this element, so that you can continue modifying the element + */ + public Element appendTo(Node parent) { + Validate.notNull(parent); + if (this.parentNode != null) { + this.parentNode.removeChild(this); + } + + parent.ensureChildNodes(); + parent.childNodes.add(this); + setSiblingIndex(parent.childNodes.size() - 1); + setParentNode(parent); + return this; + } + /** * Add a node to the start of this element's children. * From a689425d5aa3b6007038a5098add0034cbb6fb3d Mon Sep 17 00:00:00 2001 From: Serdar Kuzucu Date: Wed, 2 Dec 2015 10:30:27 +0200 Subject: [PATCH 002/774] Add test for Element.appendTo(Element parent) method. --- .../java/org/jsoup/nodes/ElementTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index c52942f8af..3fe26f9edf 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2,11 +2,12 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; -import org.jsoup.helper.StringUtil; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; import org.junit.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; import java.util.ArrayList; @@ -850,4 +851,21 @@ public void testHashAndEquals() { assertEquals("http://example2.com/four/", els.get(3).absUrl("href")); assertEquals("https://example2.com/five/", els.get(4).absUrl("href")); } + + @Test + public void testAppendTo() { + String parentHtml = "
"; + String childHtml = "
"; + + Element parentElement = Jsoup.parse(parentHtml).getElementsByClass("a").first(); + Element childElement = Jsoup.parse(childHtml).getElementsByClass("b").first(); + + childElement.attr("class", "test-class").appendTo(parentElement).attr("id", "testId"); + assertEquals("test-class", childElement.attr("class")); + assertEquals("testId", childElement.attr("id")); + assertThat(parentElement.attr("id"), not(equalTo("testId"))); + assertThat(parentElement.attr("class"), not(equalTo("test-class"))); + assertSame(childElement, parentElement.children().first()); + assertSame(parentElement, childElement.parent()); + } } From 084c510945598fda3659035cce69649aabe85759 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 16 Apr 2016 16:38:58 -0700 Subject: [PATCH 003/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f9c3f65406..a2e05023db 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.9.1 + 1.9.2-SNAPSHOT jsoup HTML parser http://jsoup.org/ 2009 @@ -24,7 +24,7 @@ http://github.com/jhy/jsoup scm:git:http://github.com/jhy/jsoup.git - jsoup-1.9.1 + HEAD Jonathan Hedley From 47f00ba674ce14c0fcd7203b4dd322074e039fb5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 16 Apr 2016 17:18:35 -0700 Subject: [PATCH 004/774] [maven-release-plugin] rollback the release of jsoup-1.9.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a2e05023db..6e0677ce0a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.9.2-SNAPSHOT + 1.8.4-SNAPSHOT jsoup HTML parser http://jsoup.org/ 2009 From 1c18964e801b78c47605651c013ccbec8b87e924 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 16 Apr 2016 17:21:47 -0700 Subject: [PATCH 005/774] Javadoc 1.8 fix --- pom.xml | 1 + release.properties | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 release.properties diff --git a/pom.xml b/pom.xml index 6e0677ce0a..e22c34a238 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,7 @@ maven-javadoc-plugin 2.6.1 + -Xdoclint:none diff --git a/release.properties b/release.properties deleted file mode 100644 index dfb36fcbcc..0000000000 --- a/release.properties +++ /dev/null @@ -1,11 +0,0 @@ -#release configuration -#Sun Aug 02 13:14:42 PDT 2015 -scm.tagNameFormat=@{project.artifactId}-@{project.version} -pushChanges=true -scm.url=scm\:git\:http\://github.com/jhy/jsoup.git -preparationGoals=clean verify -projectVersionPolicyId=default -remoteTagging=true -scm.commentPrefix=[maven-release-plugin] -exec.snapshotReleasePluginAllowed=false -completedPhase=check-poms From 2a2e8421c2aca89a21c786aa85af215dcbde02d7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 16 Apr 2016 17:22:25 -0700 Subject: [PATCH 006/774] [maven-release-plugin] prepare release jsoup-1.9.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e22c34a238..7a0d3eacf4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.8.4-SNAPSHOT + 1.9.1 jsoup HTML parser http://jsoup.org/ 2009 @@ -24,7 +24,7 @@ http://github.com/jhy/jsoup scm:git:http://github.com/jhy/jsoup.git - HEAD + jsoup-1.9.1 Jonathan Hedley From e71c258c09fdc87aaf199a27a8d72d27c85ec1ed Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 16 Apr 2016 17:50:16 -0700 Subject: [PATCH 007/774] Changelog date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8a9a28caa6..a092baf81b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.8.4 [PENDING] +*** Release 1.9.1 [2016-Apr-16] * Added support for HTTP and SOCKS request proxies, specifiable per connection. From 6ee95c90cb737438b486cae6a199b326b20848d1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 17 Apr 2016 19:55:33 -0700 Subject: [PATCH 008/774] Update POM, javadoc --- pom.xml | 12 ++++++------ src/main/java/org/jsoup/Connection.java | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7a0d3eacf4..359509310c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,9 +5,9 @@ org.jsoup jsoup - 1.9.1 + 1.9.2-SNAPSHOT jsoup HTML parser - http://jsoup.org/ + https://jsoup.org/ 2009 GitHub @@ -16,15 +16,15 @@ The MIT License - http://jsoup.org/license + https://jsoup.org/license repo - http://github.com/jhy/jsoup - scm:git:http://github.com/jhy/jsoup.git + https://github.com/jhy/jsoup + scm:git:https://github.com/jhy/jsoup.git - jsoup-1.9.1 + HEAD Jonathan Hedley diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 6c68f56823..c70a98f04f 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -215,7 +215,7 @@ public final boolean hasBody() { * .requestBody(json) * .header("Content-Type", "application/json") * .post(); - * If any data key/vals are supplied, they will be send as URL query params. + * If any data key/vals are supplied, they will be sent as URL query params. * @return this Request, for chaining */ Connection requestBody(String body); @@ -562,7 +562,7 @@ interface Request extends Base { * .requestBody(json) * .header("Content-Type", "application/json") * .post(); - * If any data key/vals are supplied, they will be send as URL query params. + * If any data key/vals are supplied, they will be sent as URL query params. * @return this Request, for chaining */ Request requestBody(String body); From f5ddda0a3ed0c0a8adf4e74dcb230bb609a2717f Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Tue, 19 Apr 2016 04:59:06 +0200 Subject: [PATCH 009/774] Tag namespaces cause cssSelector() to fail (#677) --- src/main/java/org/jsoup/nodes/Element.java | 4 +++- src/test/java/org/jsoup/nodes/ElementTest.java | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index e0dc7423c2..c6ea7fa0c4 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -498,7 +498,9 @@ public String cssSelector() { if (id().length() > 0) return "#" + id(); - StringBuilder selector = new StringBuilder(tagName()); + // Translate HTML namespace ns:tag to CSS namespace syntax ns|tag + String tagName = tagName().replace(':', '|'); + StringBuilder selector = new StringBuilder(tagName); String classes = StringUtil.join(classNames(), "."); if (classes.length() > 0) selector.append('.').append(classes); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 73e4db7715..f756e09c88 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -891,4 +891,14 @@ public void testHashcodeIsStableWithContentChanges() { root.appendChild(new Element(Tag.valueOf("a"), "")); assertTrue(set.contains(root)); } + + @Test + public void testNamespacedElements() { + // Namespaces with ns:tag in HTML must be translated to ns|tag in CSS. + String html = ""; + Document doc = Jsoup.parse(html, "http://example.com/bar/"); + Elements els = doc.select("fb|comments"); + assertEquals(1, els.size()); + assertEquals("html > body > fb|comments", els.get(0).cssSelector()); + } } From 35c6076caaab2861e793cfb4019842b35519960a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 18 Apr 2016 20:01:58 -0700 Subject: [PATCH 010/774] Changelog for #677 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index a092baf81b..bdbee319f5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ jsoup changelog +*** Release 1.9.2 [PENDING] + * Fixed an issue where namespaced tags (like ) would cause Element.cssSelector() to fail. + + *** Release 1.9.1 [2016-Apr-16] * Added support for HTTP and SOCKS request proxies, specifiable per connection. From 99286c8986c92ef12861710c0cbf5cd51b8c93f0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 23 Apr 2016 12:37:12 -0700 Subject: [PATCH 011/774] Cleaned a few dupe --- .../java/org/jsoup/parser/TokeniserState.java | 106 ++++++++---------- 1 file changed, 45 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 4947af5832..a4b7ada503 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -33,12 +33,7 @@ void read(Tokeniser t, CharacterReader r) { CharacterReferenceInData { // from & in data void read(Tokeniser t, CharacterReader r) { - char[] c = t.consumeCharacterReference(null, false); - if (c == null) - t.emit('&'); - else - t.emit(c); - t.transition(Data); + readCharRef(t, Data); } }, Rcdata { @@ -68,54 +63,17 @@ void read(Tokeniser t, CharacterReader r) { }, CharacterReferenceInRcdata { void read(Tokeniser t, CharacterReader r) { - char[] c = t.consumeCharacterReference(null, false); - if (c == null) - t.emit('&'); - else - t.emit(c); - t.transition(Rcdata); + readCharRef(t, Rcdata); } }, Rawtext { void read(Tokeniser t, CharacterReader r) { - switch (r.current()) { - case '<': - t.advanceTransition(RawtextLessthanSign); - break; - case nullChar: - t.error(this); - r.advance(); - t.emit(replacementChar); - break; - case eof: - t.emit(new Token.EOF()); - break; - default: - String data = r.consumeToAny('<', nullChar); - t.emit(data); - break; - } + readData(t, r, this, RawtextLessthanSign); } }, ScriptData { void read(Tokeniser t, CharacterReader r) { - switch (r.current()) { - case '<': - t.advanceTransition(ScriptDataLessthanSign); - break; - case nullChar: - t.error(this); - r.advance(); - t.emit(replacementChar); - break; - case eof: - t.emit(new Token.EOF()); - break; - default: - String data = r.consumeToAny('<', nullChar); - t.emit(data); - break; - } + readData(t, r, this, ScriptDataLessthanSign); } }, PLAINTEXT { @@ -304,13 +262,7 @@ void read(Tokeniser t, CharacterReader r) { }, RawtextEndTagOpen { void read(Tokeniser t, CharacterReader r) { - if (r.matchesLetter()) { - t.createTagPending(false); - t.transition(RawtextEndTagName); - } else { - t.emit(" Date: Sat, 23 Apr 2016 12:53:14 -0700 Subject: [PATCH 012/774] Bit of a cleanup --- .../jsoup/helper/DescendableLinkedList.java | 4 ---- src/main/java/org/jsoup/nodes/Document.java | 2 -- src/main/java/org/jsoup/nodes/Node.java | 1 - .../org/jsoup/parser/HtmlTreeBuilder.java | 1 - src/main/java/org/jsoup/select/Evaluator.java | 21 +++++++++---------- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DescendableLinkedList.java b/src/main/java/org/jsoup/helper/DescendableLinkedList.java index 01bed86005..833a6a220b 100644 --- a/src/main/java/org/jsoup/helper/DescendableLinkedList.java +++ b/src/main/java/org/jsoup/helper/DescendableLinkedList.java @@ -21,7 +21,6 @@ public DescendableLinkedList() { * Add a new element to the start of the list. * @param e element to add */ - @Override public void push(E e) { addFirst(e); } @@ -30,7 +29,6 @@ public void push(E e) { * Look at the last element, if there is one. * @return the last element, or null */ - @Override public E peekLast() { return size() == 0 ? null : getLast(); } @@ -39,7 +37,6 @@ public E peekLast() { * Remove and return the last element, if there is one * @return the last element, or null */ - @Override public E pollLast() { return size() == 0 ? null : removeLast(); } @@ -48,7 +45,6 @@ public E pollLast() { * Get an iterator that starts and the end of the list and works towards the start. * @return an iterator that starts and the end of the list and works towards the start. */ - @Override public Iterator descendingIterator() { return new DescendingIterator(size()); } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 6e7d721026..c7bfb1e332 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -354,8 +354,6 @@ private void ensureMetaCharsetElement() { prependChild(decl); } - } else { - // Unsupported syntax - nothing to do yet } } } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 012df0ed96..fb718f2e5f 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -607,7 +607,6 @@ public boolean hasSameValue(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Node node = (Node) o; return this.outerHtml().equals(((Node) o).outerHtml()); } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 4ac8870c2e..b3fd8f5a5b 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -13,7 +13,6 @@ */ public class HtmlTreeBuilder extends TreeBuilder { // tag searches - private static final String[] TagsScriptStyle = new String[]{"script", "style"}; public static final String[] TagsSearchInScope = new String[]{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}; private static final String[] TagSearchList = new String[]{"ol", "ul"}; private static final String[] TagSearchButton = new String[]{"button"}; diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 606b8294a5..4283a67202 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -491,10 +491,10 @@ public IsNthOfType(int a, int b) { protected int calculatePosition(Element root, Element element) { int pos = 0; Elements family = element.parent().children(); - for (int i = 0; i < family.size(); i++) { - if (family.get(i).tag().equals(element.tag())) pos++; - if (family.get(i) == element) break; - } + for (Element el : family) { + if (el.tag().equals(element.tag())) pos++; + if (el == element) break; + } return pos; } @@ -579,9 +579,9 @@ public boolean matches(Element root, Element element) { int pos = 0; Elements family = p.children(); - for (int i = 0; i < family.size(); i++) { - if (family.get(i).tag().equals(element.tag())) pos++; - } + for (Element el : family) { + if (el.tag().equals(element.tag())) pos++; + } return pos == 1; } @Override @@ -594,10 +594,9 @@ public static final class IsEmpty extends Evaluator { @Override public boolean matches(Element root, Element element) { List family = element.childNodes(); - for (int i = 0; i < family.size(); i++) { - Node n = family.get(i); - if (!(n instanceof Comment || n instanceof XmlDeclaration || n instanceof DocumentType)) return false; - } + for (Node n : family) { + if (!(n instanceof Comment || n instanceof XmlDeclaration || n instanceof DocumentType)) return false; + } return true; } @Override From 4c4192d33ff0ee27b9f293658c5a93fbd54e92ac Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 28 Apr 2016 09:26:32 -0700 Subject: [PATCH 013/774] Fixed .not() example Fixes #703 --- src/main/java/org/jsoup/select/Elements.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 80eb185d72..24f7c23435 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -412,7 +412,7 @@ public Elements select(String query) { * Remove elements from this list that match the {@link Selector} query. *

* E.g. HTML: {@code

Two
}
- * Elements divs = doc.select("div").not("#logo");
+ * Elements divs = doc.select("div").not(".logo");
* Result: {@code divs: [
Two
]} *

* @param query the selector query whose results should be removed from these elements From 49db84f8f2beb846fb8271fa20a28271afe37d90 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 5 May 2016 20:44:57 -0700 Subject: [PATCH 014/774] Fix getting stuck on non-ascii tags Fixed #704 --- CHANGES | 4 ++++ src/main/java/org/jsoup/parser/CharacterReader.java | 4 ++-- src/test/java/org/jsoup/parser/HtmlParserTest.java | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bdbee319f5..ad9870c71e 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ jsoup changelog * Fixed an issue where namespaced tags (like ) would cause Element.cssSelector() to fail. + * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character + would cause the parser to get stuck. + + *** Release 1.9.1 [2016-Apr-16] * Added support for HTTP and SOCKS request proxies, specifiable per connection. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index a34311adaf..f6d5fcd1ab 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -194,7 +194,7 @@ String consumeLetterSequence() { int start = pos; while (pos < length) { char c = input[pos]; - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) pos++; else break; @@ -207,7 +207,7 @@ String consumeLetterThenDigitSequence() { int start = pos; while (pos < length) { char c = input[pos]; - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) pos++; else break; diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 3dbddb6d30..e41eba5869 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -892,4 +892,11 @@ public void testInvalidTableContents() throws IOException { els = doc.select("русский-тэг"); assertEquals("Correct", els.text()); } + + @Test public void testSupportsPartiallyNonAsciiTags() { + String body = "

Check
"; + Document doc = Jsoup.parse(body); + Elements els = doc.select("div"); + assertEquals("Check", els.text()); + } } From 2bca40cf59bfda485f2fbf43b8bd66509a8f98ab Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 5 May 2016 20:52:32 -0700 Subject: [PATCH 015/774] Fix test --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index e41eba5869..fc7b060100 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -894,7 +894,7 @@ public void testInvalidTableContents() throws IOException { } @Test public void testSupportsPartiallyNonAsciiTags() { - String body = "
Check
"; + String body = "
Check"; Document doc = Jsoup.parse(body); Elements els = doc.select("div"); assertEquals("Check", els.text()); From 4eb4f2b2e88a2f9e6c5c1e8d0477060954f24218 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 May 2016 16:11:01 -0700 Subject: [PATCH 016/774] Detect charset from xml prolog Fixes #701 --- CHANGES | 3 + src/main/java/org/jsoup/helper/DataUtil.java | 104 ++++++++++-------- src/main/java/org/jsoup/nodes/Document.java | 2 +- .../java/org/jsoup/nodes/XmlDeclaration.java | 53 ++++----- .../java/org/jsoup/parser/XmlTreeBuilder.java | 8 +- .../java/org/jsoup/nodes/DocumentTest.java | 8 +- .../org/jsoup/parser/XmlTreeBuilderTest.java | 28 ++++- src/test/resources/htmltests/xml-charset.xml | 2 + 8 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 src/test/resources/htmltests/xml-charset.xml diff --git a/CHANGES b/CHANGES index ad9870c71e..3622923028 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.9.2 [PENDING] + * In XML documents, detect the charset from the XML prolog - + + * Fixed an issue where namespaced tags (like ) would cause Element.cssSelector() to fail. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 3b12eab8a7..6567e368c1 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -2,9 +2,15 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.XmlDeclaration; import org.jsoup.parser.Parser; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; @@ -90,51 +96,38 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba Document doc = null; // look for BOM - overrides any other header or input - byteData.mark(); - byte[] bom = new byte[4]; - if (byteData.remaining() >= bom.length) { - byteData.get(bom); - byteData.rewind(); - } - if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF || // BE - bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { // LE - charsetName = "UTF-32"; // and I hope it's on your system - } else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF || // BE - bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) { - charsetName = "UTF-16"; // in all Javas - } else if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) { - charsetName = "UTF-8"; // in all Javas - byteData.position(3); // 16 and 32 decoders consume the BOM to determine be/le; utf-8 should be consumed - } + charsetName = detectCharsetFromBom(byteData, charsetName); - if (charsetName == null) { // determine from meta. safe parse as UTF-8 + if (charsetName == null) { // determine from meta. safe first parse as UTF-8 // look for or HTML5 docData = Charset.forName(defaultCharset).decode(byteData).toString(); doc = parser.parseInput(docData, baseUri); Element meta = doc.select("meta[http-equiv=content-type], meta[charset]").first(); - if (meta != null) { // if not found, will keep utf-8 as best attempt - String foundCharset = null; + String foundCharset = null; // if not found, will keep utf-8 as best attempt + if (meta != null) { if (meta.hasAttr("http-equiv")) { foundCharset = getCharsetFromContentType(meta.attr("content")); } if (foundCharset == null && meta.hasAttr("charset")) { - try { - if (Charset.isSupported(meta.attr("charset"))) { - foundCharset = meta.attr("charset"); - } - } catch (IllegalCharsetNameException e) { - foundCharset = null; - } + foundCharset = meta.attr("charset"); } - - if (foundCharset != null && foundCharset.length() != 0 && !foundCharset.equals(defaultCharset)) { // need to re-decode - foundCharset = foundCharset.trim().replaceAll("[\"']", ""); - charsetName = foundCharset; - byteData.rewind(); - docData = Charset.forName(foundCharset).decode(byteData).toString(); - doc = null; + } + // look for + if (foundCharset == null && doc.childNode(0) instanceof XmlDeclaration) { + XmlDeclaration prolog = (XmlDeclaration) doc.childNode(0); + if (prolog.name().equals("xml")) { + foundCharset = prolog.attr("encoding"); } } + foundCharset = validateCharset(foundCharset); + + if (foundCharset != null && !foundCharset.equals(defaultCharset)) { // need to re-decode + foundCharset = foundCharset.trim().replaceAll("[\"']", ""); + charsetName = foundCharset; + byteData.rewind(); + docData = Charset.forName(foundCharset).decode(byteData).toString(); + doc = null; + } } else { // specified by content type header (or by user on file load) Validate.notEmpty(charsetName, "Must set charset arg to character set of file to parse. Set to null to attempt to detect from HTML"); docData = Charset.forName(charsetName).decode(byteData).toString(); @@ -209,15 +202,20 @@ static String getCharsetFromContentType(String contentType) { if (m.find()) { String charset = m.group(1).trim(); charset = charset.replace("charset=", ""); - if (charset.length() == 0) return null; - try { - if (Charset.isSupported(charset)) return charset; - charset = charset.toUpperCase(Locale.ENGLISH); - if (Charset.isSupported(charset)) return charset; - } catch (IllegalCharsetNameException e) { - // if our advanced charset matching fails.... we just take the default - return null; - } + return validateCharset(charset); + } + return null; + } + + private static String validateCharset(String cs) { + if (cs == null || cs.length() == 0) return null; + cs = cs.trim().replaceAll("[\"']", ""); + try { + if (Charset.isSupported(cs)) return cs; + cs = cs.toUpperCase(Locale.ENGLISH); + if (Charset.isSupported(cs)) return cs; + } catch (IllegalCharsetNameException e) { + // if our this charset matching fails.... we just take the default } return null; } @@ -233,4 +231,24 @@ static String mimeBoundary() { } return mime.toString(); } + + private static String detectCharsetFromBom(ByteBuffer byteData, String charsetName) { + byteData.mark(); + byte[] bom = new byte[4]; + if (byteData.remaining() >= bom.length) { + byteData.get(bom); + byteData.rewind(); + } + if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF || // BE + bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { // LE + charsetName = "UTF-32"; // and I hope it's on your system + } else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF || // BE + bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) { + charsetName = "UTF-16"; // in all Javas + } else if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) { + charsetName = "UTF-8"; // in all Javas + byteData.position(3); // 16 and 32 decoders consume the BOM to determine be/le; utf-8 should be consumed here + } + return charsetName; + } } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index c7bfb1e332..5751622d38 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -332,7 +332,7 @@ private void ensureMetaCharsetElement() { if (node instanceof XmlDeclaration) { XmlDeclaration decl = (XmlDeclaration) node; - if (decl.attr(XmlDeclaration.DECL_KEY).equals("xml")) { + if (decl.name().equals("xml")) { decl.attr("encoding", charset().displayName()); final String version = decl.attr("version"); diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index 10acfc240e..619cd14a94 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -1,5 +1,7 @@ package org.jsoup.nodes; +import org.jsoup.helper.Validate; + import java.io.IOException; /** @@ -7,18 +9,19 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class XmlDeclaration extends Node { - static final String DECL_KEY = "declaration"; + private final String name; private final boolean isProcessingInstruction; // 1 ) { - StringBuilder sb = new StringBuilder(decl); - final String version = attributes.get("version"); - - if( version != null ) { - sb.append(" version=\"").append(version).append("\""); - } - - final String encoding = attributes.get("encoding"); - - if( encoding != null ) { - sb.append(" encoding=\"").append(encoding).append("\""); - } - - return sb.toString(); - } - else { - return attributes.get(DECL_KEY); - } + return attributes.html().trim(); // attr html starts with a " " } void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { accum - .append("<") - .append(isProcessingInstruction ? "!" : "?") - .append(getWholeDeclaration()) - .append(">"); + .append("<") + .append(isProcessingInstruction ? "!" : "?") + .append(name); + attributes.html(accum, out); + accum + .append(isProcessingInstruction ? "!" : "?") + .append(">"); } void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 2c9cb0d03e..3ccd7dfc6c 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -1,5 +1,6 @@ package org.jsoup.parser; +import org.jsoup.Jsoup; import org.jsoup.helper.Validate; import org.jsoup.nodes.*; @@ -70,10 +71,13 @@ void insert(Token.Comment commentToken) { Comment comment = new Comment(commentToken.getData(), baseUri); Node insert = comment; if (commentToken.bogus) { // xml declarations are emitted as bogus comments (which is right for html, but not xml) + // so we do a bit of a hack and parse the data as an element to pull the attributes out String data = comment.getData(); if (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))) { - String declaration = data.substring(1); - insert = new XmlDeclaration(declaration, comment.baseUri(), data.startsWith("!")); + Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri, Parser.xmlParser()); + Element el = doc.child(0); + insert = new XmlDeclaration(el.tagName(), comment.baseUri(), data.startsWith("!")); + insert.attributes().addAll(el.attributes()); } } insertNode(insert); diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 48c3ccffc2..95b9700f5b 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -312,7 +312,7 @@ public void testMetaCharsetUpdateXmlUtf8() { doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetUtf8)); - final String xmlCharsetUTF8 = "\n" + + final String xmlCharsetUTF8 = "\n" + "\n" + " node\n" + ""; @@ -330,7 +330,7 @@ public void testMetaCharsetUpdateXmlIso8859() { doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetIso8859)); - final String xmlCharsetISO = "\n" + + final String xmlCharsetISO = "\n" + "\n" + " node\n" + ""; @@ -348,7 +348,7 @@ public void testMetaCharsetUpdateXmlNoCharset() { doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetUtf8)); - final String xmlCharsetUTF8 = "\n" + + final String xmlCharsetUTF8 = "\n" + "\n" + " node\n" + ""; @@ -372,7 +372,7 @@ public void testMetaCharsetUpdateXmlDisabled() { public void testMetaCharsetUpdateXmlDisabledNoChanges() { final Document doc = createXmlDocument("dontTouch", "dontTouch", true); - final String xmlCharset = "\n" + + final String xmlCharset = "\n" + "\n" + " node\n" + ""; diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index cb8c6f7b5b..0759c7b16d 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -6,6 +6,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; +import org.jsoup.nodes.XmlDeclaration; import org.junit.Ignore; import org.junit.Test; @@ -17,7 +18,8 @@ import java.util.List; import static org.jsoup.nodes.Document.OutputSettings.Syntax; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /** * Tests XmlTreeBuilder. @@ -103,7 +105,7 @@ public void testDoesNotForceSelfClosingKnownTags() { @Test public void handlesXmlDeclarationAsDeclaration() { String html = "One"; Document doc = Jsoup.parse(html, "", Parser.xmlParser()); - assertEquals(" One ", + assertEquals(" One ", StringUtil.normaliseWhitespace(doc.outerHtml())); assertEquals("#declaration", doc.childNode(0).nodeName()); assertEquals("#comment", doc.childNode(2).nodeName()); @@ -130,4 +132,26 @@ public void testDoesHandleEOFInTag() { Document xmlDoc = Jsoup.parse(html, "", Parser.xmlParser()); assertEquals("", xmlDoc.html()); } + + @Test + public void testDetectCharsetEncodingDeclaration() throws IOException, URISyntaxException { + File xmlFile = new File(XmlTreeBuilder.class.getResource("/htmltests/xml-charset.xml").toURI()); + InputStream inStream = new FileInputStream(xmlFile); + Document doc = Jsoup.parse(inStream, null, "http://example.com/", Parser.xmlParser()); + assertEquals("ISO-8859-1", doc.charset().name()); + assertEquals(" äöåéü", + TextUtil.stripNewlines(doc.html())); + } + + @Test + public void testParseDeclarationAttributes() { + String xml = "One"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + XmlDeclaration decl = (XmlDeclaration) doc.childNode(0); + assertEquals("1", decl.attr("version")); + assertEquals("UTF-8", decl.attr("encoding")); + assertEquals("else", decl.attr("something")); + assertEquals("version=\"1\" encoding=\"UTF-8\" something=\"else\"", decl.getWholeDeclaration()); + assertEquals("", decl.outerHtml()); + } } diff --git a/src/test/resources/htmltests/xml-charset.xml b/src/test/resources/htmltests/xml-charset.xml new file mode 100644 index 0000000000..0672d2445a --- /dev/null +++ b/src/test/resources/htmltests/xml-charset.xml @@ -0,0 +1,2 @@ + + From c090381c55b6d275eebe60053d36f198ffe793ca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 May 2016 16:18:24 -0700 Subject: [PATCH 017/774] Test for prolog Fixes #652 --- CHANGES | 3 +++ .../java/org/jsoup/parser/XmlTreeBuilderTest.java | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGES b/CHANGES index 3622923028..de2b50af28 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ jsoup changelog * In XML documents, detect the charset from the XML prolog - + * Fixed an issue where created XML documents would have an incorrect prolog. + + * Fixed an issue where namespaced tags (like ) would cause Element.cssSelector() to fail. diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 0759c7b16d..e7b37e77d1 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.util.List; import static org.jsoup.nodes.Document.OutputSettings.Syntax; @@ -154,4 +155,16 @@ public void testParseDeclarationAttributes() { assertEquals("version=\"1\" encoding=\"UTF-8\" something=\"else\"", decl.getWholeDeclaration()); assertEquals("", decl.outerHtml()); } + + @Test + public void testCreatesValidProlog() { + Document document = Document.createShell(""); + document.outputSettings().syntax(Syntax.xml); + document.charset(Charset.forName("utf-8")); + assertEquals("\n" + + "\n" + + " \n" + + " \n" + + "", document.outerHtml()); + } } From a229d7354da5210a728ce5d43158d5cd780772db Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 May 2016 17:18:58 -0700 Subject: [PATCH 018/774] Allow selectors to contain unbalanced braces Fixes #611 --- CHANGES | 4 ++++ src/main/java/org/jsoup/parser/TokenQueue.java | 7 ++++++- src/test/java/org/jsoup/select/SelectorTest.java | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index de2b50af28..1da06cb40d 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,10 @@ jsoup changelog * Fixed an issue where created XML documents would have an incorrect prolog. + * Fixed an issue where you could not use an attribute selector to find values containing unbalanced braces or + parentheses. + + * Fixed an issue where namespaced tags (like ) would cause Element.cssSelector() to fail. diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 034a9fa1c1..47947fa516 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -250,7 +250,7 @@ public String chompToIgnoreCase(String seq) { /** * Pulls a balanced string off the queue. E.g. if queue is "(one (two) three) four", (,) will return "one (two) three", - * and leave " four" on the queue. Unbalanced openers and closers can be escaped (with \). Those escapes will be left + * and leave " four" on the queue. Unbalanced openers and closers can quoted (with ' or ") or escaped (with \). Those escapes will be left * in the returned string, which is suitable for regexes (where we need to preserve the escape), but unsuitable for * contains text strings; use unescape for that. * @param open opener @@ -262,11 +262,16 @@ public String chompBalanced(char open, char close) { int end = -1; int depth = 0; char last = 0; + boolean inQuote = false; do { if (isEmpty()) break; Character c = consume(); if (last == 0 || last != ESC) { + if (c.equals('\'') || c.equals('"') && c != open) + inQuote = !inQuote; + if (inQuote) + continue; if (c.equals(open)) { depth++; if (start == -1) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 82921427e7..c687b79728 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -661,4 +661,11 @@ public void selectClassWithSpace() { Elements subSelect = els.select(":contains(one)"); assertEquals(2, subSelect.size()); } + + @Test public void attributeWithBrackets() { + String html = "
One
Two
"; + Document doc = Jsoup.parse(html); + assertEquals("One", doc.select("div[data='End]'").first().text()); + assertEquals("Two", doc.select("div[data='[Another)]]'").first().text()); + } } From d374092e9903cf912fc4ad71b77fd947a6784b20 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 May 2016 19:43:48 -0700 Subject: [PATCH 019/774] Clarify precedence --- src/main/java/org/jsoup/parser/TokenQueue.java | 2 +- src/test/java/org/jsoup/select/SelectorTest.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 47947fa516..e69f1bf4c7 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -268,7 +268,7 @@ public String chompBalanced(char open, char close) { if (isEmpty()) break; Character c = consume(); if (last == 0 || last != ESC) { - if (c.equals('\'') || c.equals('"') && c != open) + if ((c.equals('\'') || c.equals('"')) && c != open) inQuote = !inQuote; if (inQuote) continue; diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index c687b79728..8b5d7664e9 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -667,5 +667,7 @@ public void selectClassWithSpace() { Document doc = Jsoup.parse(html); assertEquals("One", doc.select("div[data='End]'").first().text()); assertEquals("Two", doc.select("div[data='[Another)]]'").first().text()); + assertEquals("One", doc.select("div[data=\"End]\"").first().text()); + assertEquals("Two", doc.select("div[data=\"[Another)]]\"").first().text()); } } From 1b51e91028efca053cbcf5c2543c86696a72c9b3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 17 May 2016 19:42:21 -0700 Subject: [PATCH 020/774] Changelog update for release --- CHANGES | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 1da06cb40d..2af84a3c67 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog -*** Release 1.9.2 [PENDING] +*** Release 1.9.2 [2016-May-17] + * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character + would cause the parser to get stuck in an infinite loop. + + * In XML documents, detect the charset from the XML prolog - @@ -14,10 +18,6 @@ jsoup changelog * Fixed an issue where namespaced tags (like ) would cause Element.cssSelector() to fail. - * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character - would cause the parser to get stuck. - - *** Release 1.9.1 [2016-Apr-16] * Added support for HTTP and SOCKS request proxies, specifiable per connection. From 5dc4c487d42705f60ff6e5b95d03c71a3326d6cd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 17 May 2016 19:43:55 -0700 Subject: [PATCH 021/774] [maven-release-plugin] prepare release jsoup-1.9.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 359509310c..ce7ce8054c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.9.2-SNAPSHOT + 1.9.2 jsoup HTML parser https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.9.2 Jonathan Hedley From 9655d8959c6df403c3900a4950adc1358b89b774 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 17 May 2016 19:44:00 -0700 Subject: [PATCH 022/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ce7ce8054c..e287e3bf4c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.9.2 + 1.9.3-SNAPSHOT jsoup HTML parser https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.9.2 + HEAD Jonathan Hedley From f6a1ef346c74ca3305ef5f198ac953a4fc208813 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 20 May 2016 13:54:58 -0700 Subject: [PATCH 023/774] Update links to https://jsoup.org/ --- README | 2 +- pom.xml | 2 +- .../java/org/jsoup/examples/package-info.java | 2 +- src/main/javadoc/overview.html | 2 +- .../org/jsoup/integration/UrlConnectTest.java | 24 +++++++++---------- src/test/java/org/jsoup/nodes/NodeTest.java | 20 ++++++++-------- .../java/org/jsoup/select/ElementsTest.java | 8 +++---- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/README b/README index 8f2bf3a36d..19bf7c4ff5 100644 --- a/README +++ b/README @@ -14,4 +14,4 @@ jsoup is designed to deal with all varieties of HTML found in the wild; from pri jsoup runs on Java 1.5 and up. -See http://jsoup.org/ for downloads and documentation. +See https://jsoup.org/ for downloads and documentation. diff --git a/pom.xml b/pom.xml index e287e3bf4c..b73a04cc67 100644 --- a/pom.xml +++ b/pom.xml @@ -123,7 +123,7 @@ - http://jsoup.org/ + https://jsoup.org/ diff --git a/src/main/java/org/jsoup/examples/package-info.java b/src/main/java/org/jsoup/examples/package-info.java index c312f430d4..90fef53b7f 100644 --- a/src/main/java/org/jsoup/examples/package-info.java +++ b/src/main/java/org/jsoup/examples/package-info.java @@ -1,4 +1,4 @@ /** - Contains example programs and use of jsoup. See the jsoup cookbook. + Contains example programs and use of jsoup. See the jsoup cookbook. */ package org.jsoup.examples; \ No newline at end of file diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index a218b0363b..bbeac18a06 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -23,7 +23,7 @@

jsoup: Java HTML parser that makes sense of real-world HTML soup.

jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; jsoup will create a sensible parse tree.

-

See jsoup.org for downloads, documentation, and examples...

+

See jsoup.org for downloads, documentation, and examples...

@author Jonathan Hedley diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 79a6d755bf..c1574923a8 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -40,7 +40,7 @@ public class UrlConnectTest { @Test public void fetchURl() throws IOException { - String url = "http://jsoup.org"; // no trailing / to force redir + String url = "https://jsoup.org"; // no trailing / to force redir Document doc = Jsoup.parse(new URL(url), 10*1000); assertTrue(doc.title().contains("jsoup")); } @@ -95,7 +95,7 @@ public void exceptOnUnsupportedProtocol(){ @Test public void ignoresContentTypeIfSoConfigured() throws IOException { - Document doc = Jsoup.connect("http://jsoup.org/rez/osi_logo.png").ignoreContentType(true).get(); + Document doc = Jsoup.connect("https://jsoup.org/rez/osi_logo.png").ignoreContentType(true).get(); assertEquals("", doc.title()); // this will cause an ugly parse tree } @@ -184,7 +184,7 @@ public void followsNewTempRedirect() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/307.pl"); // http://jsoup.org Document doc = con.get(); assertTrue(doc.title().contains("jsoup")); - assertEquals("http://jsoup.org", con.response().url().toString()); + assertEquals("https://jsoup.org", con.response().url().toString()); } @Test @@ -193,7 +193,7 @@ public void postRedirectsFetchWithGet() throws IOException { .data("Argument", "Riposte") .method(Connection.Method.POST); Connection.Response res = con.execute(); - assertEquals("http://jsoup.org", res.url().toExternalForm()); + assertEquals("https://jsoup.org", res.url().toExternalForm()); assertEquals(Connection.Method.GET, res.method()); } @@ -303,7 +303,7 @@ public void doesntRedirectIfSoConfigured() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/302.pl").followRedirects(false); Connection.Response res = con.execute(); assertEquals(302, res.statusCode()); - assertEquals("http://jsoup.org", res.header("Location")); + assertEquals("https://jsoup.org", res.header("Location")); } @Test @@ -529,12 +529,12 @@ public void postJpeg() throws IOException { @Test public void handles201Created() throws IOException { Document doc = Jsoup.connect("http://direct.infohound.net/tools/201.pl").get(); // 201, location=jsoup - assertEquals("http://jsoup.org", doc.location()); + assertEquals("https://jsoup.org", doc.location()); } @Test public void fetchToW3c() throws IOException { - String url = "http://jsoup.org"; + String url = "https://jsoup.org"; Document doc = Jsoup.connect(url).get(); W3CDom dom = new W3CDom(); @@ -595,7 +595,7 @@ public void sendHeadRequest() throws IOException { @Test public void fetchViaHttpProxy() throws IOException { - String url = "http://jsoup.org"; + String url = "https://jsoup.org"; Proxy proxy = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("localhost", 8888)); Document doc = Jsoup.connect(url).proxy(proxy).get(); assertTrue(doc.title().contains("jsoup")); @@ -603,7 +603,7 @@ public void fetchViaHttpProxy() throws IOException { @Test public void fetchViaHttpProxySetByArgument() throws IOException { - String url = "http://jsoup.org"; + String url = "https://jsoup.org"; Document doc = Jsoup.connect(url).proxy("localhost", 8888).get(); assertTrue(doc.title().contains("jsoup")); } @@ -611,7 +611,7 @@ public void fetchViaHttpProxySetByArgument() throws IOException { @Test public void invalidProxyFails() throws IOException { boolean caught = false; - String url = "http://jsoup.org"; + String url = "https://jsoup.org"; try { Document doc = Jsoup.connect(url).proxy("localhost", 8889).get(); } catch (IOException e) { @@ -622,7 +622,7 @@ public void invalidProxyFails() throws IOException { @Test public void proxyGetAndSet() throws IOException { - String url = "http://jsoup.org"; + String url = "https://jsoup.org"; Proxy proxy = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("localhost", 8889)); // invalid final Connection con = Jsoup.connect(url).proxy(proxy); @@ -635,7 +635,7 @@ public void proxyGetAndSet() throws IOException { @Test public void throwsIfRequestBodyForGet() throws IOException { boolean caught = false; - String url = "http://jsoup.org"; + String url = "https://jsoup.org"; try { Document doc = Jsoup.connect(url).requestBody("fail").get(); } catch (IllegalArgumentException e) { diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 760dcfed7b..a9274dbc15 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -36,7 +36,7 @@ public class NodeTest { @Test public void setBaseUriIsRecursive() { Document doc = Jsoup.parse("

"); - String baseUri = "http://jsoup.org"; + String baseUri = "https://jsoup.org"; doc.setBaseUri(baseUri); assertEquals(baseUri, doc.baseUri()); @@ -45,23 +45,23 @@ public class NodeTest { } @Test public void handlesAbsPrefix() { - Document doc = Jsoup.parse("Hello", "http://jsoup.org/"); + Document doc = Jsoup.parse("Hello", "https://jsoup.org/"); Element a = doc.select("a").first(); assertEquals("/foo", a.attr("href")); - assertEquals("http://jsoup.org/foo", a.attr("abs:href")); + assertEquals("https://jsoup.org/foo", a.attr("abs:href")); assertTrue(a.hasAttr("abs:href")); } @Test public void handlesAbsOnImage() { - Document doc = Jsoup.parse("

", "http://jsoup.org/"); + Document doc = Jsoup.parse("

", "https://jsoup.org/"); Element img = doc.select("img").first(); - assertEquals("http://jsoup.org/rez/osi_logo.png", img.attr("abs:src")); + assertEquals("https://jsoup.org/rez/osi_logo.png", img.attr("abs:src")); assertEquals(img.absUrl("src"), img.attr("abs:src")); } @Test public void handlesAbsPrefixOnHasAttr() { // 1: no abs url; 2: has abs url - Document doc = Jsoup.parse("One Two"); + Document doc = Jsoup.parse("One Two"); Element one = doc.select("#1").first(); Element two = doc.select("#2").first(); @@ -71,7 +71,7 @@ public class NodeTest { assertTrue(two.hasAttr("abs:href")); assertTrue(two.hasAttr("href")); - assertEquals("http://jsoup.org/", two.absUrl("href")); + assertEquals("https://jsoup.org/", two.absUrl("href")); } @Test public void literalAbsPrefix() { @@ -116,13 +116,13 @@ public void handlesAbsOnProtocolessAbsoluteUris() { Test for an issue with Java's abs URL handler. */ @Test public void absHandlesRelativeQuery() { - Document doc = Jsoup.parse("One Two", "http://jsoup.org/path/file?bar"); + Document doc = Jsoup.parse("One Two", "https://jsoup.org/path/file?bar"); Element a1 = doc.select("a").first(); - assertEquals("http://jsoup.org/path/file?foo", a1.absUrl("href")); + assertEquals("https://jsoup.org/path/file?foo", a1.absUrl("href")); Element a2 = doc.select("a").get(1); - assertEquals("http://jsoup.org/path/bar.html?foo", a2.absUrl("href")); + assertEquals("https://jsoup.org/path/bar.html?foo", a2.absUrl("href")); } @Test public void absHandlesDotFromIndex() { diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index 5b971ae307..f20df7f887 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -52,7 +52,7 @@ public class ElementsTest { } @Test public void hasAbsAttr() { - Document doc = Jsoup.parse("One Two"); + Document doc = Jsoup.parse("One Two"); Elements one = doc.select("#1"); Elements two = doc.select("#2"); Elements both = doc.select("a"); @@ -68,14 +68,14 @@ public class ElementsTest { } @Test public void absAttr() { - Document doc = Jsoup.parse("One Two"); + Document doc = Jsoup.parse("One Two"); Elements one = doc.select("#1"); Elements two = doc.select("#2"); Elements both = doc.select("a"); assertEquals("", one.attr("abs:href")); - assertEquals("http://jsoup.org", two.attr("abs:href")); - assertEquals("http://jsoup.org", both.attr("abs:href")); + assertEquals("https://jsoup.org", two.attr("abs:href")); + assertEquals("https://jsoup.org", both.attr("abs:href")); } @Test public void classes() { From ec75e400f6ec713a20c709eef9db46d78b24ab1b Mon Sep 17 00:00:00 2001 From: VEINHORN Date: Fri, 17 Jun 2016 13:23:24 +0300 Subject: [PATCH 024/774] bump to junit 4.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b73a04cc67..4726b7fa2a 100644 --- a/pom.xml +++ b/pom.xml @@ -195,7 +195,7 @@ junit junit - 4.5 + 4.12 test From 5d00201aec621e81a37447e32baf1a82040ee8e4 Mon Sep 17 00:00:00 2001 From: Eric Peters Date: Tue, 28 Jun 2016 10:19:53 -0700 Subject: [PATCH 025/774] Add wildcard-namespace selector support '*|' Issue #723 --- .../java/org/jsoup/parser/TokenQueue.java | 4 ++-- .../org/jsoup/select/CombiningEvaluator.java | 2 ++ src/main/java/org/jsoup/select/Evaluator.java | 22 +++++++++++++++++++ .../java/org/jsoup/select/QueryParser.java | 16 +++++++++----- src/main/java/org/jsoup/select/Selector.java | 1 + .../java/org/jsoup/select/SelectorTest.java | 21 ++++++++++++++++++ 6 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index e69f1bf4c7..4e04c72e9f 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -346,13 +346,13 @@ public String consumeTagName() { } /** - * Consume a CSS element selector (tag name, but | instead of : for namespaces, to not conflict with :pseudo selects). + * Consume a CSS element selector (tag name, but | instead of : for namespaces (or *| for wildcard namespace), to not conflict with :pseudo selects). * * @return tag name */ public String consumeElementSelector() { int start = pos; - while (!isEmpty() && (matchesWord() || matchesAny('|', '_', '-'))) + while (!isEmpty() && (matchesWord() || matchesAny("*|","|", "_", "-"))) pos++; return queue.substring(start, pos); diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index e25668ecab..e3616f4689 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -77,6 +77,8 @@ static final class Or extends CombiningEvaluator { updateNumEvaluators(); } + Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); } + Or() { super(); } diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 4283a67202..1a8b4598ab 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -51,6 +51,28 @@ public String toString() { } } + + /** + * Evaluator for tag name that ends with + */ + public static final class TagEndsWith extends Evaluator { + private String tagName; + + public TagEndsWith(String tagName) { + this.tagName = tagName; + } + + @Override + public boolean matches(Element root, Element element) { + return (element.tagName().endsWith(tagName)); + } + + @Override + public String toString() { + return String.format("%s", tagName); + } + } + /** * Evaluator for element id */ diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 66a899c843..3aa8b98508 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -144,7 +144,7 @@ private void findElements() { byId(); else if (tq.matchChomp(".")) byClass(); - else if (tq.matchesWord()) + else if (tq.matchesWord() || tq.matches("*|")) byTag(); else if (tq.matches("[")) byAttribute(); @@ -211,13 +211,19 @@ private void byClass() { private void byTag() { String tagName = tq.consumeElementSelector(); + Validate.notEmpty(tagName); - // namespaces: if element name is "abc:def", selector must be "abc|def", so flip: - if (tagName.contains("|")) - tagName = tagName.replace("|", ":"); + // namespaces: wildcard match equals(tagName) or ending in ":"+tagName + if (tagName.startsWith("*|")) { + evals.add(new CombiningEvaluator.Or(new Evaluator.Tag(tagName.trim().toLowerCase()), new Evaluator.TagEndsWith(tagName.replace("*|", ":").trim().toLowerCase()))); + } else { + // namespaces: if element name is "abc:def", selector must be "abc|def", so flip: + if (tagName.contains("|")) + tagName = tagName.replace("|", ":"); - evals.add(new Evaluator.Tag(tagName.trim().toLowerCase())); + evals.add(new Evaluator.Tag(tagName.trim().toLowerCase())); + } } private void byAttribute() { diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 6dc395403c..c87e4b4dc5 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -23,6 +23,7 @@ * PatternMatchesExample * *any element* * tagelements with the given tag namediv + * *|Eelements of type E in any namespace ns*|name finds <fb:name> elements * ns|Eelements of type E in the namespace nsfb|name finds <fb:name> elements * #idelements with attribute ID of "id"div#wrap, #logo * .classelements with a class name of "class"div.left, .result diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 8b5d7664e9..c2d628ca0d 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -109,6 +109,27 @@ public class SelectorTest { assertEquals("2", byContains.last().id()); } + @Test public void testWildcardNamespacedTag() { + Document doc = Jsoup.parse("
Hello
There"); + Elements byTag = doc.select("*|def"); + assertEquals(2, byTag.size()); + assertEquals("1", byTag.first().id()); + assertEquals("2", byTag.last().id()); + + Elements byAttr = doc.select(".bold"); + assertEquals(1, byAttr.size()); + assertEquals("2", byAttr.last().id()); + + Elements byTagAttr = doc.select("*|def.bold"); + assertEquals(1, byTagAttr.size()); + assertEquals("2", byTagAttr.last().id()); + + Elements byContains = doc.select("*|def:contains(e)"); + assertEquals(2, byContains.size()); + assertEquals("1", byContains.first().id()); + assertEquals("2", byContains.last().id()); + } + @Test public void testByAttributeStarting() { Document doc = Jsoup.parse("
Hello

There

No

"); Elements withData = doc.select("[^data-]"); From 0b8debbcedd32187c48feeda69e3c3d28357cb71 Mon Sep 17 00:00:00 2001 From: zhujiajun Date: Wed, 29 Jun 2016 22:05:12 +0800 Subject: [PATCH 026/774] Add Connection.headers(map) --- src/main/java/org/jsoup/Connection.java | 8 ++++++++ src/main/java/org/jsoup/helper/HttpConnection.java | 8 ++++++++ .../java/org/jsoup/helper/HttpConnectionTest.java | 12 ++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index c70a98f04f..017a6cac96 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -229,6 +229,14 @@ public final boolean hasBody() { */ Connection header(String name, String value); + /** + * Adds each of the supplied headers to the request. + * @param headers map of headers name {@literal ->} value pairs + * @return this Connection, for chaining + * @see org.jsoup.Connection.Request#headers() + */ + Connection headers(Map headers); + /** * Set a cookie to be sent in the request. * @param name name of cookie diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 77fb660831..cb2738f8f1 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -193,6 +193,14 @@ public Connection header(String name, String value) { return this; } + public Connection headers(Map headers) { + Validate.notNull(headers, "Header map must not be null"); + for (Map.Entry entry : headers.entrySet()) { + req.header(entry.getKey(),entry.getValue()); + } + return this; + } + public Connection cookie(String name, String value) { req.cookie(name, value); return this; diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index c535395633..842828af49 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -52,6 +52,18 @@ public class HttpConnectionTest { assertEquals("deflate", res.header("accept-Encoding")); } + @Test public void headers() { + Connection con = HttpConnection.connect("http://example.com"); + Map headers = new HashMap(); + headers.put("content-type", "text/html"); + headers.put("Connection", "keep-alive"); + headers.put("Host", "http://example.com"); + con.headers(headers); + assertEquals("text/html", con.request().header("content-type")); + assertEquals("keep-alive", con.request().header("Connection")); + assertEquals("http://example.com", con.request().header("Host")); + } + @Test public void sameHeadersCombineWithComma() { Map> headers = new HashMap>(); List values = new ArrayList(); From 6569bb7a3671cb7241c6fcb6e0f1eef7a39f7a1d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 5 Aug 2016 15:05:22 -0700 Subject: [PATCH 027/774] Introduced ParseSettings for tag and attribute name case options. --- CHANGES | 6 ++ src/main/java/org/jsoup/nodes/Attribute.java | 8 +- src/main/java/org/jsoup/nodes/Attributes.java | 93 ++++++++++++---- src/main/java/org/jsoup/nodes/Document.java | 5 +- src/main/java/org/jsoup/nodes/Element.java | 11 +- src/main/java/org/jsoup/nodes/Node.java | 15 +-- .../org/jsoup/parser/HtmlTreeBuilder.java | 22 ++-- .../jsoup/parser/HtmlTreeBuilderState.java | 100 +++++++++--------- .../java/org/jsoup/parser/ParseSettings.java | 59 +++++++++++ src/main/java/org/jsoup/parser/Parser.java | 19 +++- src/main/java/org/jsoup/parser/Tag.java | 20 +++- src/main/java/org/jsoup/parser/Token.java | 11 +- src/main/java/org/jsoup/parser/Tokeniser.java | 6 +- .../java/org/jsoup/parser/TokeniserState.java | 20 ++-- .../java/org/jsoup/parser/TreeBuilder.java | 14 +-- .../java/org/jsoup/parser/XmlTreeBuilder.java | 24 +++-- src/main/java/org/jsoup/select/Evaluator.java | 7 +- .../java/org/jsoup/select/QueryParser.java | 4 +- src/main/java/org/jsoup/select/Selector.java | 2 +- .../java/org/jsoup/nodes/AttributesTest.java | 12 ++- .../java/org/jsoup/nodes/ElementTest.java | 5 +- .../java/org/jsoup/parser/HtmlParserTest.java | 27 +++++ .../org/jsoup/parser/ParserSettingsTest.java | 27 +++++ src/test/java/org/jsoup/parser/TagTest.java | 10 +- .../org/jsoup/parser/XmlTreeBuilderTest.java | 25 ++++- .../java/org/jsoup/select/SelectorTest.java | 9 +- 26 files changed, 411 insertions(+), 150 deletions(-) create mode 100644 src/main/java/org/jsoup/parser/ParseSettings.java create mode 100644 src/test/java/org/jsoup/parser/ParserSettingsTest.java diff --git a/CHANGES b/CHANGES index 2af84a3c67..5ca94705a6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,11 @@ jsoup changelog +*** Release 1.10.1 [PENDING] + * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML + parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve + case, according to the relevant spec. The CSS selectors for tags and attributes remain case insensitive, per the CSS + spec. + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 7f6096cdbc..25ca4cec97 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -24,14 +24,14 @@ public class Attribute implements Map.Entry, Cloneable { /** * Create a new attribute from unencoded (raw) key and value. - * @param key attribute key + * @param key attribute key; case is preserved. * @param value attribute value * @see #createFromEncoded */ public Attribute(String key, String value) { Validate.notEmpty(key); Validate.notNull(value); - this.key = key.trim().toLowerCase(); + this.key = key.trim(); this.value = value; } @@ -44,12 +44,12 @@ public String getKey() { } /** - Set the attribute key. Gets normalised as per the constructor method. + Set the attribute key; case is preserved. @param key the new key; must not be null */ public void setKey(String key) { Validate.notEmpty(key); - this.key = key.trim().toLowerCase(); + this.key = key.trim(); } /** diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 28b013e294..cc21fb6ef3 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -4,30 +4,39 @@ import org.jsoup.helper.Validate; import java.io.IOException; -import java.util.*; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * The attributes of an Element. *

- * Attributes are treated as a map: there can be only one value associated with an attribute key. + * Attributes are treated as a map: there can be only one value associated with an attribute key/name. *

*

- * Attribute key and value comparisons are done case insensitively, and keys are normalised to - * lower-case. + * Attribute name and value comparisons are case sensitive. By default for HTML, attribute names are + * normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by + * name. *

- * + * * @author Jonathan Hedley, jonathan@hedley.net */ public class Attributes implements Iterable, Cloneable { protected static final String dataPrefix = "data-"; - + private LinkedHashMap attributes = null; // linked hash map to preserve insertion order. // null be default as so many elements have no attributes -- saves a good chunk of memory /** Get an attribute value by key. - @param key the attribute key + @param key the (case-sensitive) attribute key @return the attribute value if set; or empty string if not set. @see #hasKey(String) */ @@ -37,10 +46,27 @@ public String get(String key) { if (attributes == null) return ""; - Attribute attr = attributes.get(key.toLowerCase()); + Attribute attr = attributes.get(key); return attr != null ? attr.getValue() : ""; } + /** + * Get an attribute's value by case-insensitive key + * @param key the attribute name + * @return the first matching attribute value if set; or empty string if not set. + */ + public String getIgnoreCase(String key) { + Validate.notEmpty(key); + if (attributes == null) + return ""; + + for (String attrKey : attributes.keySet()) { + if (attrKey.equalsIgnoreCase(key)) + return attributes.get(attrKey).getValue(); + } + return ""; + } + /** Set a new attribute, or replace an existing one by key. @param key attribute key @@ -50,7 +76,7 @@ public void put(String key, String value) { Attribute attr = new Attribute(key, value); put(attr); } - + /** Set a new boolean attribute, remove attribute if value is false. @param key attribute key @@ -75,23 +101,52 @@ public void put(Attribute attribute) { } /** - Remove an attribute by key. + Remove an attribute by key. Case sensitive. @param key attribute key to remove */ public void remove(String key) { Validate.notEmpty(key); if (attributes == null) return; - attributes.remove(key.toLowerCase()); + attributes.remove(key); + } + + /** + Remove an attribute by key. Case insensitive. + @param key attribute key to remove + */ + public void removeIgnoreCase(String key) { + Validate.notEmpty(key); + if (attributes == null) + return; + for (String attrKey : attributes.keySet()) { + if (attrKey.equalsIgnoreCase(key)) + attributes.remove(attrKey); + } } /** Tests if these attributes contain an attribute with this key. - @param key key to check for + @param key case-sensitive key to check for @return true if key exists, false otherwise */ public boolean hasKey(String key) { - return attributes != null && attributes.containsKey(key.toLowerCase()); + return attributes != null && attributes.containsKey(key); + } + + /** + Tests if these attributes contain an attribute with this key. + @param key key to check for + @return true if key exists, false otherwise + */ + public boolean hasKeyIgnoreCase(String key) { + if (attributes == null) + return false; + for (String attrKey : attributes.keySet()) { + if (attrKey.equalsIgnoreCase(key)) + return true; + } + return false; } /** @@ -115,7 +170,7 @@ public void addAll(Attributes incoming) { attributes = new LinkedHashMap(incoming.size()); attributes.putAll(incoming.attributes); } - + public Iterator iterator() { return asList().iterator(); } @@ -159,18 +214,18 @@ public String html() { } return accum.toString(); } - + void html(Appendable accum, Document.OutputSettings out) throws IOException { if (attributes == null) return; - + for (Map.Entry entry : attributes.entrySet()) { Attribute attribute = entry.getValue(); accum.append(" "); attribute.html(accum, out); } } - + @Override public String toString() { return html(); @@ -185,9 +240,9 @@ public String toString() { public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Attributes)) return false; - + Attributes that = (Attributes) o; - + return !(attributes != null ? !attributes.equals(that.attributes) : that.attributes != null); } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 5751622d38..c558c4c65f 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -2,6 +2,7 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; @@ -27,7 +28,7 @@ public class Document extends Element { @see #createShell */ public Document(String baseUri) { - super(Tag.valueOf("#root"), baseUri); + super(Tag.valueOf("#root", ParseSettings.htmlDefault), baseUri); this.location = baseUri; } @@ -103,7 +104,7 @@ public void title(String title) { @return new element */ public Element createElement(String tagName) { - return new Element(Tag.valueOf(tagName), this.baseUri()); + return new Element(Tag.valueOf(tagName, ParseSettings.preserveCase), this.baseUri()); } /** diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index c6ea7fa0c4..205e4101b4 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -2,6 +2,7 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Collector; @@ -58,7 +59,7 @@ public Element(Tag tag, String baseUri, Attributes attributes) { * @param tag element tag * @param baseUri the base URI of this element. It is acceptable for the base URI to be an empty * string, but not null. - * @see Tag#valueOf(String) + * @see Tag#valueOf(String, ParseSettings) */ public Element(Tag tag, String baseUri) { this(tag, baseUri, new Attributes()); @@ -87,7 +88,7 @@ public String tagName() { */ public Element tagName(String tagName) { Validate.notEmpty(tagName, "Tag name must not be empty."); - tag = Tag.valueOf(tagName); + tag = Tag.valueOf(tagName, ParseSettings.preserveCase); // preserve the requested tag case return this; } @@ -116,7 +117,7 @@ public boolean isBlock() { * @return The id attribute, if present, or an empty string if not. */ public String id() { - return attributes.get("id"); + return attributes.getIgnoreCase("id"); } /** @@ -668,7 +669,7 @@ public Elements getElementsByClass(String className) { */ public Elements getElementsByAttribute(String key) { Validate.notEmpty(key); - key = key.trim().toLowerCase(); + key = key.trim(); return Collector.collect(new Evaluator.Attribute(key), this); } @@ -681,7 +682,7 @@ public Elements getElementsByAttribute(String key) { */ public Elements getElementsByAttributeStarting(String keyPrefix) { Validate.notEmpty(keyPrefix); - keyPrefix = keyPrefix.trim().toLowerCase(); + keyPrefix = keyPrefix.trim(); return Collector.collect(new Evaluator.AttributeStarting(keyPrefix), this); } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index fb718f2e5f..124f73934c 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -58,7 +58,7 @@ Get the node name of this node. Use for debugging purposes and not logic switchi public abstract String nodeName(); /** - * Get an attribute's value by its key. + * Get an attribute's value by its key. Case insensitive *

* To get an absolute URL from an attribute that may be a relative URL, prefix the key with abs, * which is a shortcut to the {@link #absUrl} method. @@ -75,8 +75,9 @@ Get the node name of this node. Use for debugging purposes and not logic switchi public String attr(String attributeKey) { Validate.notNull(attributeKey); - if (attributes.hasKey(attributeKey)) - return attributes.get(attributeKey); + String val = attributes.getIgnoreCase(attributeKey); + if (val.length() > 0) + return val; else if (attributeKey.toLowerCase().startsWith("abs:")) return absUrl(attributeKey.substring("abs:".length())); else return ""; @@ -102,7 +103,7 @@ public Node attr(String attributeKey, String attributeValue) { } /** - * Test if this element has an attribute. + * Test if this element has an attribute. Case insensitive * @param attributeKey The attribute key to check. * @return true if the attribute exists, false if not. */ @@ -111,10 +112,10 @@ public boolean hasAttr(String attributeKey) { if (attributeKey.startsWith("abs:")) { String key = attributeKey.substring("abs:".length()); - if (attributes.hasKey(key) && !absUrl(key).equals("")) + if (attributes.hasKeyIgnoreCase(key) && !absUrl(key).equals("")) return true; } - return attributes.hasKey(attributeKey); + return attributes.hasKeyIgnoreCase(attributeKey); } /** @@ -124,7 +125,7 @@ public boolean hasAttr(String attributeKey) { */ public Node removeAttr(String attributeKey) { Validate.notNull(attributeKey); - attributes.remove(attributeKey); + attributes.removeIgnoreCase(attributeKey); return this; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index b3fd8f5a5b..2078a9b984 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -45,17 +45,21 @@ public class HtmlTreeBuilder extends TreeBuilder { HtmlTreeBuilder() {} + ParseSettings defaultSettings() { + return ParseSettings.htmlDefault; + } + @Override - Document parse(String input, String baseUri, ParseErrorList errors) { + Document parse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { state = HtmlTreeBuilderState.Initial; baseUriSetFromDoc = false; - return super.parse(input, baseUri, errors); + return super.parse(input, baseUri, errors, settings); } - List parseFragment(String inputFragment, Element context, String baseUri, ParseErrorList errors) { + List parseFragment(String inputFragment, Element context, String baseUri, ParseErrorList errors, ParseSettings settings) { // context may be null state = HtmlTreeBuilderState.Initial; - initialiseParse(inputFragment, baseUri, errors); + initialiseParse(inputFragment, baseUri, errors, settings); contextElement = context; fragmentParsing = true; Element root = null; @@ -79,7 +83,7 @@ else if (contextTag.equals("plaintext")) else tokeniser.transition(TokeniserState.Data); // default - root = new Element(Tag.valueOf("html"), baseUri); + root = new Element(Tag.valueOf("html", settings), baseUri); doc.appendChild(root); stack.add(root); resetInsertionMode(); @@ -178,13 +182,13 @@ Element insert(Token.StartTag startTag) { return el; } - Element el = new Element(Tag.valueOf(startTag.name()), baseUri, startTag.attributes); + Element el = new Element(Tag.valueOf(startTag.name(), settings), baseUri, settings.normalizeAttributes(startTag.attributes)); insert(el); return el; } Element insertStartTag(String startTagName) { - Element el = new Element(Tag.valueOf(startTagName), baseUri); + Element el = new Element(Tag.valueOf(startTagName, settings), baseUri); insert(el); return el; } @@ -195,7 +199,7 @@ void insert(Element el) { } Element insertEmpty(Token.StartTag startTag) { - Tag tag = Tag.valueOf(startTag.name()); + Tag tag = Tag.valueOf(startTag.name(), settings); Element el = new Element(tag, baseUri, startTag.attributes); insertNode(el); if (startTag.isSelfClosing()) { @@ -211,7 +215,7 @@ Element insertEmpty(Token.StartTag startTag) { } FormElement insertForm(Token.StartTag startTag, boolean onStack) { - Tag tag = Tag.valueOf(startTag.name()); + Tag tag = Tag.valueOf(startTag.name(), settings); FormElement el = new FormElement(tag, baseUri, startTag.attributes); setFormElement(el); insertNode(el); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 1d6d4e3626..9a4081405a 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -19,7 +19,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { // todo: parse error check on expected doctypes // todo: quirk state check on doctype ids Token.Doctype d = t.asDoctype(); - DocumentType doctype = new DocumentType(d.getName(), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri()); + DocumentType doctype = new DocumentType( + tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri()); tb.getDocument().appendChild(doctype); if (d.isForceQuirks()) tb.getDocument().quirksMode(Document.QuirksMode.quirks); @@ -41,10 +42,10 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.insert(t.asComment()); } else if (isWhitespace(t)) { return true; // ignore whitespace - } else if (t.isStartTag() && t.asStartTag().name().equals("html")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { tb.insert(t.asStartTag()); tb.transition(BeforeHead); - } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().name(), "head", "body", "html", "br"))) { + } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().normalName(), "head", "body", "html", "br"))) { return anythingElse(t, tb); } else if (t.isEndTag()) { tb.error(this); @@ -70,13 +71,13 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isDoctype()) { tb.error(this); return false; - } else if (t.isStartTag() && t.asStartTag().name().equals("html")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return InBody.process(t, tb); // does not transition - } else if (t.isStartTag() && t.asStartTag().name().equals("head")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("head")) { Element head = tb.insert(t.asStartTag()); tb.setHeadElement(head); tb.transition(InHead); - } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().name(), "head", "body", "html", "br"))) { + } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().normalName(), "head", "body", "html", "br"))) { tb.processStartTag("head"); return tb.process(t); } else if (t.isEndTag()) { @@ -104,7 +105,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; case StartTag: Token.StartTag start = t.asStartTag(); - String name = start.name(); + String name = start.normalName(); if (name.equals("html")) { return InBody.process(t, tb); } else if (StringUtil.in(name, "base", "basefont", "bgsound", "command", "link")) { @@ -139,7 +140,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { break; case EndTag: Token.EndTag end = t.asEndTag(); - name = end.name(); + name = end.normalName(); if (name.equals("head")) { tb.pop(); tb.transition(AfterHead); @@ -165,17 +166,17 @@ private boolean anythingElse(Token t, TreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isDoctype()) { tb.error(this); - } else if (t.isStartTag() && t.asStartTag().name().equals("html")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return tb.process(t, InBody); - } else if (t.isEndTag() && t.asEndTag().name().equals("noscript")) { + } else if (t.isEndTag() && t.asEndTag().normalName().equals("noscript")) { tb.pop(); tb.transition(InHead); - } else if (isWhitespace(t) || t.isComment() || (t.isStartTag() && StringUtil.in(t.asStartTag().name(), + } else if (isWhitespace(t) || t.isComment() || (t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "basefont", "bgsound", "link", "meta", "noframes", "style"))) { return tb.process(t, InHead); - } else if (t.isEndTag() && t.asEndTag().name().equals("br")) { + } else if (t.isEndTag() && t.asEndTag().normalName().equals("br")) { return anythingElse(t, tb); - } else if ((t.isStartTag() && StringUtil.in(t.asStartTag().name(), "head", "noscript")) || t.isEndTag()) { + } else if ((t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "head", "noscript")) || t.isEndTag()) { tb.error(this); return false; } else { @@ -200,7 +201,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.error(this); } else if (t.isStartTag()) { Token.StartTag startTag = t.asStartTag(); - String name = startTag.name(); + String name = startTag.normalName(); if (name.equals("html")) { return tb.process(t, InBody); } else if (name.equals("body")) { @@ -223,7 +224,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { anythingElse(t, tb); } } else if (t.isEndTag()) { - if (StringUtil.in(t.asEndTag().name(), "body", "html")) { + if (StringUtil.in(t.asEndTag().normalName(), "body", "html")) { anythingElse(t, tb); } else { tb.error(this); @@ -270,7 +271,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } case StartTag: Token.StartTag startTag = t.asStartTag(); - String name = startTag.name(); + String name = startTag.normalName(); if (name.equals("a")) { if (tb.getActiveFormattingElement("a") != null) { tb.error(this); @@ -555,7 +556,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { case EndTag: Token.EndTag endTag = t.asEndTag(); - name = endTag.name(); + name = endTag.normalName(); if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { // Adoption Agency Algorithm. for (int i = 0; i < 8; i++) { @@ -608,7 +609,8 @@ else if (!tb.onStack(formatEl)) { } else if (node == formatEl) break; - Element replacement = new Element(Tag.valueOf(node.nodeName()), tb.getBaseUri()); + Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); + // case will follow the original node (so honours ParseSettings) tb.replaceActiveFormattingElement(node, replacement); tb.replaceOnStack(node, replacement); node = replacement; @@ -759,7 +761,7 @@ else if (!tb.onStack(formatEl)) { } boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { - String name = t.asEndTag().name(); + String name = t.asEndTag().normalName(); ArrayList stack = tb.getStack(); for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); @@ -813,7 +815,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else if (t.isStartTag()) { Token.StartTag startTag = t.asStartTag(); - String name = startTag.name(); + String name = startTag.normalName(); if (name.equals("caption")) { tb.clearStackToTableContext(); tb.insertMarkerToFormattingElements(); @@ -859,7 +861,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return true; // todo: check if should return processed http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intable } else if (t.isEndTag()) { Token.EndTag endTag = t.asEndTag(); - String name = endTag.name(); + String name = endTag.normalName(); if (name.equals("table")) { if (!tb.inTableScope(name)) { @@ -937,9 +939,9 @@ boolean process(Token t, HtmlTreeBuilder tb) { }, InCaption { boolean process(Token t, HtmlTreeBuilder tb) { - if (t.isEndTag() && t.asEndTag().name().equals("caption")) { + if (t.isEndTag() && t.asEndTag().normalName().equals("caption")) { Token.EndTag endTag = t.asEndTag(); - String name = endTag.name(); + String name = endTag.normalName(); if (!tb.inTableScope(name)) { tb.error(this); return false; @@ -952,15 +954,15 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.transition(InTable); } } else if (( - t.isStartTag() && StringUtil.in(t.asStartTag().name(), + t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr") || - t.isEndTag() && t.asEndTag().name().equals("table")) + t.isEndTag() && t.asEndTag().normalName().equals("table")) ) { tb.error(this); boolean processed = tb.processEndTag("caption"); if (processed) return tb.process(t); - } else if (t.isEndTag() && StringUtil.in(t.asEndTag().name(), + } else if (t.isEndTag() && StringUtil.in(t.asEndTag().normalName(), "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr")) { tb.error(this); return false; @@ -985,7 +987,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { break; case StartTag: Token.StartTag startTag = t.asStartTag(); - String name = startTag.name(); + String name = startTag.normalName(); if (name.equals("html")) return tb.process(t, InBody); else if (name.equals("col")) @@ -995,7 +997,7 @@ else if (name.equals("col")) break; case EndTag: Token.EndTag endTag = t.asEndTag(); - name = endTag.name(); + name = endTag.normalName(); if (name.equals("colgroup")) { if (tb.currentElement().nodeName().equals("html")) { // frag case tb.error(this); @@ -1030,7 +1032,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { switch (t.type) { case StartTag: Token.StartTag startTag = t.asStartTag(); - String name = startTag.name(); + String name = startTag.normalName(); if (name.equals("tr")) { tb.clearStackToTableBodyContext(); tb.insert(startTag); @@ -1046,7 +1048,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { break; case EndTag: Token.EndTag endTag = t.asEndTag(); - name = endTag.name(); + name = endTag.normalName(); if (StringUtil.in(name, "tbody", "tfoot", "thead")) { if (!tb.inTableScope(name)) { tb.error(this); @@ -1089,7 +1091,7 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isStartTag()) { Token.StartTag startTag = t.asStartTag(); - String name = startTag.name(); + String name = startTag.normalName(); if (StringUtil.in(name, "th", "td")) { tb.clearStackToTableRowContext(); @@ -1103,7 +1105,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } } else if (t.isEndTag()) { Token.EndTag endTag = t.asEndTag(); - String name = endTag.name(); + String name = endTag.normalName(); if (name.equals("tr")) { if (!tb.inTableScope(name)) { @@ -1150,7 +1152,7 @@ private boolean handleMissingTr(Token t, TreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isEndTag()) { Token.EndTag endTag = t.asEndTag(); - String name = endTag.name(); + String name = endTag.normalName(); if (StringUtil.in(name, "td", "th")) { if (!tb.inTableScope(name)) { @@ -1178,7 +1180,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return anythingElse(t, tb); } } else if (t.isStartTag() && - StringUtil.in(t.asStartTag().name(), + StringUtil.in(t.asStartTag().normalName(), "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr")) { if (!(tb.inTableScope("td") || tb.inTableScope("th"))) { tb.error(this); @@ -1223,7 +1225,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; case StartTag: Token.StartTag start = t.asStartTag(); - String name = start.name(); + String name = start.normalName(); if (name.equals("html")) return tb.process(start, InBody); else if (name.equals("option")) { @@ -1252,7 +1254,7 @@ else if (tb.currentElement().nodeName().equals("optgroup")) break; case EndTag: Token.EndTag end = t.asEndTag(); - name = end.name(); + name = end.normalName(); if (name.equals("optgroup")) { if (tb.currentElement().nodeName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).nodeName().equals("optgroup")) tb.processEndTag("option"); @@ -1293,13 +1295,13 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { }, InSelectInTable { boolean process(Token t, HtmlTreeBuilder tb) { - if (t.isStartTag() && StringUtil.in(t.asStartTag().name(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { + if (t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { tb.error(this); tb.processEndTag("select"); return tb.process(t); - } else if (t.isEndTag() && StringUtil.in(t.asEndTag().name(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { + } else if (t.isEndTag() && StringUtil.in(t.asEndTag().normalName(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { tb.error(this); - if (tb.inTableScope(t.asEndTag().name())) { + if (tb.inTableScope(t.asEndTag().normalName())) { tb.processEndTag("select"); return (tb.process(t)); } else @@ -1318,9 +1320,9 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isDoctype()) { tb.error(this); return false; - } else if (t.isStartTag() && t.asStartTag().name().equals("html")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return tb.process(t, InBody); - } else if (t.isEndTag() && t.asEndTag().name().equals("html")) { + } else if (t.isEndTag() && t.asEndTag().normalName().equals("html")) { if (tb.isFragmentParsing()) { tb.error(this); return false; @@ -1348,7 +1350,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else if (t.isStartTag()) { Token.StartTag start = t.asStartTag(); - String name = start.name(); + String name = start.normalName(); if (name.equals("html")) { return tb.process(start, InBody); } else if (name.equals("frameset")) { @@ -1361,7 +1363,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.error(this); return false; } - } else if (t.isEndTag() && t.asEndTag().name().equals("frameset")) { + } else if (t.isEndTag() && t.asEndTag().normalName().equals("frameset")) { if (tb.currentElement().nodeName().equals("html")) { // frag tb.error(this); return false; @@ -1392,11 +1394,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isDoctype()) { tb.error(this); return false; - } else if (t.isStartTag() && t.asStartTag().name().equals("html")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return tb.process(t, InBody); - } else if (t.isEndTag() && t.asEndTag().name().equals("html")) { + } else if (t.isEndTag() && t.asEndTag().normalName().equals("html")) { tb.transition(AfterAfterFrameset); - } else if (t.isStartTag() && t.asStartTag().name().equals("noframes")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("noframes")) { return tb.process(t, InHead); } else if (t.isEOF()) { // cool your heels, we're complete @@ -1411,7 +1413,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isComment()) { tb.insert(t.asComment()); - } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().name().equals("html"))) { + } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { return tb.process(t, InBody); } else if (t.isEOF()) { // nice work chuck @@ -1427,11 +1429,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isComment()) { tb.insert(t.asComment()); - } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().name().equals("html"))) { + } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { return tb.process(t, InBody); } else if (t.isEOF()) { // nice work chuck - } else if (t.isStartTag() && t.asStartTag().name().equals("noframes")) { + } else if (t.isStartTag() && t.asStartTag().normalName().equals("noframes")) { return tb.process(t, InHead); } else { tb.error(this); diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java new file mode 100644 index 0000000000..9ac7e6b1f9 --- /dev/null +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -0,0 +1,59 @@ +package org.jsoup.parser; + +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Attributes; + +/** + * Controls parser settings, to optionally preserve tag and/or attribute name case. + */ +public class ParseSettings { + /** + * HTML default settings: both tag and attribute names are lower-cased during parsing. + */ + public static final ParseSettings htmlDefault; + /** + * Preserve both tag and attribute case. + */ + public static final ParseSettings preserveCase; + + static { + htmlDefault = new ParseSettings(false, false); + preserveCase = new ParseSettings(true, true); + } + + private final boolean preserveTagCase; + private final boolean preserveAttributeCase; + + /** + * Define parse settings. + * @param tag preserve tag case? + * @param attribute preserve attribute name case? + */ + public ParseSettings(boolean tag, boolean attribute) { + preserveTagCase = tag; + preserveAttributeCase = attribute; + } + + String normalizeTag(String name) { + name = name.trim(); + if (!preserveTagCase) + name = name.toLowerCase(); + return name; + } + + String normalizeAttribute(String name) { + name = name.trim(); + if (!preserveAttributeCase) + name = name.toLowerCase(); + return name; + } + + Attributes normalizeAttributes(Attributes attributes) { + if (!preserveAttributeCase) { + for (Attribute attr : attributes) { + attr.setKey(attr.getKey().toLowerCase()); + } + } + return attributes; + } +} diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index 90e5c7448f..868ef41e4f 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -16,6 +16,7 @@ public class Parser { private TreeBuilder treeBuilder; private int maxErrors = DEFAULT_MAX_ERRORS; private ParseErrorList errors; + private ParseSettings settings; /** * Create a new Parser, using the specified TreeBuilder @@ -23,11 +24,12 @@ public class Parser { */ public Parser(TreeBuilder treeBuilder) { this.treeBuilder = treeBuilder; + settings = treeBuilder.defaultSettings(); } public Document parseInput(String html, String baseUri) { errors = isTrackErrors() ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking(); - return treeBuilder.parse(html, baseUri, errors); + return treeBuilder.parse(html, baseUri, errors, settings); } // gets & sets @@ -75,6 +77,15 @@ public List getErrors() { return errors; } + public Parser settings(ParseSettings settings) { + this.settings = settings; + return this; + } + + public ParseSettings settings() { + return settings; + } + // static parse functions below /** * Parse HTML into a Document. @@ -86,7 +97,7 @@ public List getErrors() { */ public static Document parse(String html, String baseUri) { TreeBuilder treeBuilder = new HtmlTreeBuilder(); - return treeBuilder.parse(html, baseUri, ParseErrorList.noTracking()); + return treeBuilder.parse(html, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); } /** @@ -101,7 +112,7 @@ public static Document parse(String html, String baseUri) { */ public static List parseFragment(String fragmentHtml, Element context, String baseUri) { HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder(); - return treeBuilder.parseFragment(fragmentHtml, context, baseUri, ParseErrorList.noTracking()); + return treeBuilder.parseFragment(fragmentHtml, context, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); } /** @@ -113,7 +124,7 @@ public static List parseFragment(String fragmentHtml, Element context, Str */ public static List parseXmlFragment(String fragmentXml, String baseUri) { XmlTreeBuilder treeBuilder = new XmlTreeBuilder(); - return treeBuilder.parseFragment(fragmentXml, baseUri, ParseErrorList.noTracking()); + return treeBuilder.parseFragment(fragmentXml, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); } /** diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 30a04402b8..646ba9b9d4 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -25,7 +25,7 @@ public class Tag { private boolean formSubmit = false; // a control that can be submitted in a form: input etc private Tag(String tagName) { - this.tagName = tagName.toLowerCase(); + this.tagName = tagName; } /** @@ -44,14 +44,15 @@ public String getName() { *

* * @param tagName Name of tag, e.g. "p". Case insensitive. + * @param settings used to control tag name sensitivity * @return The tag, either defined or new generic. */ - public static Tag valueOf(String tagName) { + public static Tag valueOf(String tagName, ParseSettings settings) { Validate.notNull(tagName); Tag tag = tags.get(tagName); if (tag == null) { - tagName = tagName.trim().toLowerCase(); + tagName = settings.normalizeTag(tagName); Validate.notEmpty(tagName); tag = tags.get(tagName); @@ -65,6 +66,19 @@ public static Tag valueOf(String tagName) { return tag; } + /** + * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything. + *

+ * Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals(). + *

+ * + * @param tagName Name of tag, e.g. "p". Case sensitive. + * @return The tag, either defined or new generic. + */ + public static Tag valueOf(String tagName) { + return valueOf(tagName, ParseSettings.preserveCase); + } + /** * Gets if this is a block tag. * diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index daff2a0703..3ea0d2d736 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -68,6 +68,7 @@ public boolean isForceQuirks() { static abstract class Tag extends Token { protected String tagName; + protected String normalName; // lc version of tag name, for case insensitive tree build private String pendingAttributeName; // attribute names are generally caught in one hop, not accumulated private StringBuilder pendingAttributeValue = new StringBuilder(); // but values are accumulated, from e.g. & in hrefs private String pendingAttributeValueS; // try to get attr vals in one shot, vs Builder @@ -79,6 +80,7 @@ static abstract class Tag extends Token { @Override Tag reset() { tagName = null; + normalName = null; pendingAttributeName = null; reset(pendingAttributeValue); pendingAttributeValueS = null; @@ -119,13 +121,18 @@ final void finaliseTag() { } } - final String name() { + final String name() { // preserves case, for input into Tag.valueOf (which may drop case) Validate.isFalse(tagName == null || tagName.length() == 0); return tagName; } + final String normalName() { // loses case, used in tree building for working out where in tree it should go + return normalName; + } + final Tag name(String name) { tagName = name; + normalName = name.toLowerCase(); return this; } @@ -141,6 +148,7 @@ final Attributes getAttributes() { // these appenders are rarely hit in not null state-- caused by null chars. final void appendTagName(String append) { tagName = tagName == null ? append : tagName.concat(append); + normalName = tagName.toLowerCase(); } final void appendTagName(char append) { @@ -206,6 +214,7 @@ Tag reset() { StartTag nameAttr(String name, Attributes attributes) { this.tagName = name; this.attributes = attributes; + normalName = tagName.toLowerCase(); return this; } diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 1797dae8e1..b62e293441 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -16,8 +16,8 @@ final class Tokeniser { Arrays.sort(notCharRefCharsSorted); } - private CharacterReader reader; // html input - private ParseErrorList errors; // errors found while tokenising + private final CharacterReader reader; // html input + private final ParseErrorList errors; // errors found while tokenising private TokeniserState state = TokeniserState.Data; // current tokenisation state private Token emitPending; // the token we are about to emit on next read @@ -218,7 +218,7 @@ void createTempBuffer() { } boolean isAppropriateEndTagToken() { - return lastStartTag != null && tagPending.tagName.equals(lastStartTag); + return lastStartTag != null && tagPending.name().equalsIgnoreCase(lastStartTag); } String appropriateEndTagName() { diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index a4b7ada503..c10dd2c1c5 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -143,7 +143,7 @@ void read(Tokeniser t, CharacterReader r) { void read(Tokeniser t, CharacterReader r) { // previous TagOpen state did NOT consume, will have a letter char in current //String tagName = r.consumeToAnySorted(tagCharsSorted).toLowerCase(); - String tagName = r.consumeTagName().toLowerCase(); + String tagName = r.consumeTagName(); t.tagPending.appendTagName(tagName); switch (r.consume()) { @@ -194,8 +194,8 @@ void read(Tokeniser t, CharacterReader r) { void read(Tokeniser t, CharacterReader r) { if (r.matchesLetter()) { t.createTagPending(false); - t.tagPending.appendTagName(Character.toLowerCase(r.current())); - t.dataBuffer.append(Character.toLowerCase(r.current())); + t.tagPending.appendTagName(r.current()); + t.dataBuffer.append(r.current()); t.advanceTransition(RCDATAEndTagName); } else { t.emit(" 1 && (data.startsWith("!") || data.startsWith("?"))) { Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri, Parser.xmlParser()); Element el = doc.child(0); - insert = new XmlDeclaration(el.tagName(), comment.baseUri(), data.startsWith("!")); + insert = new XmlDeclaration(settings.normalizeTag(el.tagName()), comment.baseUri(), data.startsWith("!")); insert.attributes().addAll(el.attributes()); } } @@ -89,7 +97,7 @@ void insert(Token.Character characterToken) { } void insert(Token.Doctype d) { - DocumentType doctypeNode = new DocumentType(d.getName(), d.getPublicIdentifier(), d.getSystemIdentifier(), baseUri); + DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), baseUri); insertNode(doctypeNode); } @@ -121,8 +129,8 @@ private void popStackToClose(Token.EndTag endTag) { } } - List parseFragment(String inputFragment, String baseUri, ParseErrorList errors) { - initialiseParse(inputFragment, baseUri, errors); + List parseFragment(String inputFragment, String baseUri, ParseErrorList errors, ParseSettings settings) { + initialiseParse(inputFragment, baseUri, errors, settings); runParser(); return doc.childNodes(); } diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 4283a67202..dba2e5d86c 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -42,7 +42,7 @@ public Tag(String tagName) { @Override public boolean matches(Element root, Element element) { - return (element.tagName().equals(tagName)); + return (element.tagName().equalsIgnoreCase(tagName)); } @Override @@ -124,14 +124,15 @@ public static final class AttributeStarting extends Evaluator { private String keyPrefix; public AttributeStarting(String keyPrefix) { - this.keyPrefix = keyPrefix; + Validate.notEmpty(keyPrefix); + this.keyPrefix = keyPrefix.toLowerCase(); } @Override public boolean matches(Element root, Element element) { List values = element.attributes().asList(); for (org.jsoup.nodes.Attribute attribute : values) { - if (attribute.getKey().startsWith(keyPrefix)) + if (attribute.getKey().toLowerCase().startsWith(keyPrefix)) return true; } return false; diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 66a899c843..f25387b300 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -206,7 +206,7 @@ private void byId() { private void byClass() { String className = tq.consumeCssIdentifier(); Validate.notEmpty(className); - evals.add(new Evaluator.Class(className.trim().toLowerCase())); + evals.add(new Evaluator.Class(className.trim())); } private void byTag() { @@ -217,7 +217,7 @@ private void byTag() { if (tagName.contains("|")) tagName = tagName.replace("|", ":"); - evals.add(new Evaluator.Tag(tagName.trim().toLowerCase())); + evals.add(new Evaluator.Tag(tagName.trim())); } private void byAttribute() { diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 6dc395403c..4ab4a522a0 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -12,7 +12,7 @@ * *

Selector syntax

*

- * A selector is a chain of simple selectors, separated by combinators. Selectors are case insensitive (including against + * A selector is a chain of simple selectors, separated by combinators. Selectors are case insensitive (including against * elements, attributes, and attribute values). *

*

diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 66e3c761f3..6f76e9298e 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -17,14 +17,20 @@ public class AttributesTest { a.put("data-name", "Jsoup"); assertEquals(3, a.size()); - assertTrue(a.hasKey("tot")); + assertTrue(a.hasKey("Tot")); assertTrue(a.hasKey("Hello")); assertTrue(a.hasKey("data-name")); + assertFalse(a.hasKey("tot")); + assertTrue(a.hasKeyIgnoreCase("tot")); + assertEquals("There", a.getIgnoreCase("hEllo")); + assertEquals(1, a.dataset().size()); assertEquals("Jsoup", a.dataset().get("name")); - assertEquals("a&p", a.get("tot")); + assertEquals("", a.get("tot")); + assertEquals("a&p", a.get("Tot")); + assertEquals("a&p", a.getIgnoreCase("tot")); - assertEquals(" tot=\"a&p\" hello=\"There\" data-name=\"Jsoup\"", a.html()); + assertEquals(" Tot=\"a&p\" Hello=\"There\" data-name=\"Jsoup\"", a.html()); assertEquals(a.html(), a.toString()); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index f756e09c88..2afb5f33c8 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -299,8 +299,9 @@ public class ElementTest { Document doc = Jsoup.parse("

Hello

"); Element div = doc.getElementById("1"); div.appendElement("p").text("there"); - div.appendElement("P").attr("class", "second").text("now"); - assertEquals("

Hello

there

now

", + div.appendElement("P").attr("CLASS", "second").text("now"); + // manually specifying tag and attributes should now preserve case, regardless of parse mode + assertEquals("

Hello

there

now

", TextUtil.stripNewlines(doc.html())); // check sibling index (with short circuit on reindexChildren): diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index fc7b060100..b3517c75c8 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -899,4 +899,31 @@ public void testInvalidTableContents() throws IOException { Elements els = doc.select("div"); assertEquals("Check", els.text()); } + + @Test public void testHtmlLowerCase() { + String html = "
One
"; + Document doc = Jsoup.parse(html); + assertEquals("
One
", StringUtil.normaliseWhitespace(doc.outerHtml())); + } + + @Test public void canPreserveTagCase() { + Parser parser = Parser.htmlParser(); + parser.settings(new ParseSettings(true, false)); + Document doc = parser.parseInput("
", ""); + assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); + } + + @Test public void canPreserveAttributeCase() { + Parser parser = Parser.htmlParser(); + parser.settings(new ParseSettings(false, true)); + Document doc = parser.parseInput("
", ""); + assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); + } + + @Test public void canPreserveBothCase() { + Parser parser = Parser.htmlParser(); + parser.settings(new ParseSettings(true, true)); + Document doc = parser.parseInput("
", ""); + assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); + } } diff --git a/src/test/java/org/jsoup/parser/ParserSettingsTest.java b/src/test/java/org/jsoup/parser/ParserSettingsTest.java new file mode 100644 index 0000000000..ee35091bbf --- /dev/null +++ b/src/test/java/org/jsoup/parser/ParserSettingsTest.java @@ -0,0 +1,27 @@ +package org.jsoup.parser; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class ParserSettingsTest { + @Test + public void caseSupport() { + ParseSettings bothOn = new ParseSettings(true, true); + ParseSettings bothOff = new ParseSettings(false, false); + ParseSettings tagOn = new ParseSettings(true, false); + ParseSettings attrOn = new ParseSettings(false, true); + + assertEquals("FOO", bothOn.normalizeTag("FOO")); + assertEquals("FOO", bothOn.normalizeAttribute("FOO")); + + assertEquals("foo", bothOff.normalizeTag("FOO")); + assertEquals("foo", bothOff.normalizeAttribute("FOO")); + + assertEquals("FOO", tagOn.normalizeTag("FOO")); + assertEquals("foo", tagOn.normalizeAttribute("FOO")); + + assertEquals("foo", attrOn.normalizeTag("FOO")); + assertEquals("FOO", attrOn.normalizeAttribute("FOO")); + + } +} diff --git a/src/test/java/org/jsoup/parser/TagTest.java b/src/test/java/org/jsoup/parser/TagTest.java index bee6400dbf..da6195fb76 100644 --- a/src/test/java/org/jsoup/parser/TagTest.java +++ b/src/test/java/org/jsoup/parser/TagTest.java @@ -8,9 +8,15 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TagTest { - @Test public void isCaseInsensitive() { + @Test public void isCaseSensitive() { Tag p1 = Tag.valueOf("P"); Tag p2 = Tag.valueOf("p"); + assertFalse(p1.equals(p2)); + } + + @Test public void canBeInsensitive() { + Tag p1 = Tag.valueOf("P", ParseSettings.htmlDefault); + Tag p2 = Tag.valueOf("p", ParseSettings.htmlDefault); assertEquals(p1, p2); } @@ -49,7 +55,7 @@ public class TagTest { } @Test public void defaultSemantics() { - Tag foo = Tag.valueOf("foo"); // not defined + Tag foo = Tag.valueOf("FOO"); // not defined Tag foo2 = Tag.valueOf("FOO"); assertEquals(foo, foo2); diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index e7b37e77d1..424640071e 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -50,10 +50,10 @@ public void testPopToClose() { @Test public void testCommentAndDocType() { - String xml = "One Two"; + String xml = "One Two"; XmlTreeBuilder tb = new XmlTreeBuilder(); Document doc = tb.parse(xml, "http://foo.com/"); - assertEquals("One Two", + assertEquals("One Two", TextUtil.stripNewlines(doc.html())); } @@ -156,6 +156,13 @@ public void testParseDeclarationAttributes() { assertEquals("", decl.outerHtml()); } + @Test + public void caseSensitiveDeclaration() { + String xml = ""; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + assertEquals("", doc.outerHtml()); + } + @Test public void testCreatesValidProlog() { Document document = Document.createShell(""); @@ -167,4 +174,18 @@ public void testCreatesValidProlog() { " \n" + "", document.outerHtml()); } + + @Test + public void preservesCaseByDefault() { + String xml = "Check"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + assertEquals("Check", TextUtil.stripNewlines(doc.html())); + } + + @Test + public void canNormalizeCase() { + String xml = "Check"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser().settings(ParseSettings.htmlDefault)); + assertEquals("Check", TextUtil.stripNewlines(doc.html())); + } } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 8b5d7664e9..69941c0e32 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -14,7 +14,8 @@ */ public class SelectorTest { @Test public void testByTag() { - Elements els = Jsoup.parse("

Hello

").select("div"); + // should be case insensitive + Elements els = Jsoup.parse("

Hello

").select("DIV"); assertEquals(3, els.size()); assertEquals("1", els.get(0).id()); assertEquals("2", els.get(1).id()); @@ -35,7 +36,7 @@ public class SelectorTest { } @Test public void testByClass() { - Elements els = Jsoup.parse("

").select("p.one"); + Elements els = Jsoup.parse("

").select("P.One"); assertEquals(2, els.size()); assertEquals("0", els.get(0).id()); assertEquals("1", els.get(1).id()); @@ -55,7 +56,7 @@ public class SelectorTest { Elements withTitle = doc.select("[title]"); assertEquals(4, withTitle.size()); - Elements foo = doc.select("[title=foo]"); + Elements foo = doc.select("[TITLE=foo]"); assertEquals(1, foo.size()); Elements foo2 = doc.select("[title=\"foo\"]"); @@ -200,7 +201,7 @@ public class SelectorTest { @Test public void descendant() { String h = "

Hello

There

None

"; Document doc = Jsoup.parse(h); - Element root = doc.getElementsByClass("head").first(); + Element root = doc.getElementsByClass("HEAD").first(); Elements els = root.select(".head p"); assertEquals(2, els.size()); From b1e58d0f5fbeb51fd214f69acad246d5d6af9cf7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 5 Aug 2016 15:28:52 -0700 Subject: [PATCH 028/774] Changelog for #724 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 2af84a3c67..ce4ca46b8c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ jsoup changelog +*** Release 1.10.1 [PENDING] + * Added support for *|E wildcard namespace selectors. + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. From 9e8ba01343f2b39185846821407e93ca5240bdb1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 5 Aug 2016 15:34:13 -0700 Subject: [PATCH 029/774] Changelog for #725 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index ce4ca46b8c..3d471a1a6b 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ jsoup changelog * Added support for *|E wildcard namespace selectors. + * Added support for setting multiple connection headers at once with Connection.headers(Map) + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. From f148f88de365c50eea28d3e14093e13c33104483 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 5 Aug 2016 16:41:40 -0700 Subject: [PATCH 030/774] Normalize invalid attribute names in XML when converting Fixes #721 --- CHANGES | 5 +++++ src/main/java/org/jsoup/helper/W3CDom.java | 5 ++++- src/test/java/org/jsoup/helper/W3CDomTest.java | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3d471a1a6b..9b8d5d5e3d 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,11 @@ jsoup changelog * Added support for setting multiple connection headers at once with Connection.headers(Map) + * Fixed an issue when converting to the W3CDom XML, where valid (but ugly) HTML attribute names containing characters + like '"' could not be converted into valid XML attribute names. These attribute names are now normalized if possible, + or not added to the XML DOM. + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 0def9a4bf6..497e3ea129 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -121,7 +121,10 @@ public void tail(org.jsoup.nodes.Node source, int depth) { private void copyAttributes(org.jsoup.nodes.Node source, Element el) { for (Attribute attribute : source.attributes()) { - el.setAttribute(attribute.getKey(), attribute.getValue()); + // valid xml attribute names are: ^[a-zA-Z_:][-a-zA-Z0-9_:.] + String key = attribute.getKey().replaceAll("[^-a-zA-Z0-9_:.]", ""); + if (key.matches("[a-zA-Z_:]{1}[-a-zA-Z0-9_:.]*")) + el.setAttribute(key, attribute.getValue()); } } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 569fbf1894..bb3e284504 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.integration.ParseTest; +import org.jsoup.nodes.Element; import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -79,5 +80,17 @@ public void namespacePreservation() throws IOException { assertEquals("section", xSection.getLocalName()); assertEquals("x:section", xSection.getNodeName()); } + + @Test + public void handlesInvalidAttributeNames() { + String html = " "; + org.jsoup.nodes.Document jsoupDoc; + jsoupDoc = Jsoup.parse(html); + Element body = jsoupDoc.select("body").first(); + assertTrue(body.hasAttr("\"")); // actually an attribute with key '"'. Correct per HTML5 spec, but w3c xml dom doesn't dig it + assertTrue(body.hasAttr("name\"")); + + Document w3Doc = new W3CDom().fromJsoup(jsoupDoc); + } } From 70f916f03dcf63ba5bbc3d70770c6adebc18d36d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 5 Aug 2016 17:11:12 -0700 Subject: [PATCH 031/774] Fragment test to verify #726 --- .../java/org/jsoup/parser/HtmlParserTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index fc7b060100..8872f6bccc 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -899,4 +899,19 @@ public void testInvalidTableContents() throws IOException { Elements els = doc.select("div"); assertEquals("Check", els.text()); } + + @Test public void testFragement() { + // make sure when parsing a body fragment, a script tag at start goes into the body + String html = + "\n" + + "
some content
\n" + + ""; + + Document body = Jsoup.parseBodyFragment(html); + assertEquals(" \n" + + "
\n" + + " some content\n" + + "
\n" + + "", body.body().html()); + } } From ec48e0cc39d86f95f7b9184aeb1891d721109e55 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 11 Aug 2016 12:57:14 -0700 Subject: [PATCH 032/774] Check bounds of document before fetching first child Fixes #727, OOB on empty body XML load. --- CHANGES | 3 +++ src/main/java/org/jsoup/helper/DataUtil.java | 2 +- .../org/jsoup/integration/UrlConnectTest.java | 22 +++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 0e76740472..5261c5d912 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ jsoup changelog or not added to the XML DOM. + * Fixed an OOB exception when loading an empty-body URL and parsing with the XML parser. + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 6567e368c1..0f44f9f000 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -113,7 +113,7 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba } } // look for - if (foundCharset == null && doc.childNode(0) instanceof XmlDeclaration) { + if (foundCharset == null && doc.childNodeSize() > 0 && doc.childNode(0) instanceof XmlDeclaration) { XmlDeclaration prolog = (XmlDeclaration) doc.childNode(0); if (prolog.name().equals("xml")) { foundCharset = prolog.attr("encoding"); diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index c1574923a8..251fdc0298 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -281,7 +281,7 @@ public void ignores500tExceptionIfSoConfigured() throws IOException { } @Test - public void ignores500NoWithContentExceptionIfSoConfigured() throws IOException { + public void ignores500WithNoContentExceptionIfSoConfigured() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/500-no-content.pl").ignoreHttpErrors(true); Connection.Response res = con.execute(); Document doc = res.parse(); @@ -290,7 +290,7 @@ public void ignores500NoWithContentExceptionIfSoConfigured() throws IOException } @Test - public void ignores200NoWithContentExceptionIfSoConfigured() throws IOException { + public void ignores200WithNoContentExceptionIfSoConfigured() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/200-no-content.pl").ignoreHttpErrors(true); Connection.Response res = con.execute(); Document doc = res.parse(); @@ -298,6 +298,24 @@ public void ignores200NoWithContentExceptionIfSoConfigured() throws IOException assertEquals("All Good", res.statusMessage()); } + @Test + public void handles200WithNoContent() throws IOException { + Connection con = Jsoup + .connect("http://direct.infohound.net/tools/200-no-content.pl") + .userAgent(browserUa); + Connection.Response res = con.execute(); + Document doc = res.parse(); + assertEquals(200, res.statusCode()); + + con = Jsoup + .connect("http://direct.infohound.net/tools/200-no-content.pl") + .parser(Parser.xmlParser()) + .userAgent(browserUa); + res = con.execute(); + doc = res.parse(); + assertEquals(200, res.statusCode()); + } + @Test public void doesntRedirectIfSoConfigured() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/302.pl").followRedirects(false); From 00c8d2b7623259246c4eb5df63494c6b42c08f85 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 14 Aug 2016 12:51:58 -0700 Subject: [PATCH 033/774] Enable charset override Fixes #743. --- CHANGES | 4 ++ src/main/java/org/jsoup/Connection.java | 9 +++- .../java/org/jsoup/helper/HttpConnection.java | 5 +++ .../org/jsoup/integration/UrlConnectTest.java | 41 +++++++++++++++++-- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 5261c5d912..6a91bdd617 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,10 @@ jsoup changelog * Added support for setting multiple connection headers at once with Connection.headers(Map) + * Added support for setting/overriding the response character set in Connection.Response, for cases where the charset + is not defined by the server, or is defined incorrectly. + + * Fixed an issue when converting to the W3CDom XML, where valid (but ugly) HTML attribute names containing characters like '"' could not be converted into valid XML attribute names. These attribute names are now normalized if possible, or not added to the XML DOM. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 017a6cac96..e89cf4d4b2 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -627,11 +627,18 @@ interface Response extends Base { String statusMessage(); /** - * Get the character set name of the response. + * Get the character set name of the response, derived from the content-type header. * @return character set name */ String charset(); + /** + * Set / override the response character set. When the document body is parsed it will be with this charset. + * @param charset to decode body as + * @return this Response, for chaining + */ + Response charset(String charset); + /** * Get the response content type (e.g. "text/html"); * @return the response content type diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index cb2738f8f1..33f755acad 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -652,6 +652,11 @@ public String charset() { return charset; } + public Response charset(String charset) { + this.charset = charset; + return this; + } + public String contentType() { return contentType; } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 251fdc0298..29bb9d17bb 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -184,7 +184,7 @@ public void followsNewTempRedirect() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/307.pl"); // http://jsoup.org Document doc = con.get(); assertTrue(doc.title().contains("jsoup")); - assertEquals("https://jsoup.org", con.response().url().toString()); + assertEquals("https://jsoup.org/", con.response().url().toString()); } @Test @@ -193,7 +193,7 @@ public void postRedirectsFetchWithGet() throws IOException { .data("Argument", "Riposte") .method(Connection.Method.POST); Connection.Response res = con.execute(); - assertEquals("https://jsoup.org", res.url().toExternalForm()); + assertEquals("https://jsoup.org/", res.url().toExternalForm()); assertEquals(Connection.Method.GET, res.method()); } @@ -321,7 +321,7 @@ public void doesntRedirectIfSoConfigured() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/302.pl").followRedirects(false); Connection.Response res = con.execute(); assertEquals(302, res.statusCode()); - assertEquals("https://jsoup.org", res.header("Location")); + assertEquals("http://jsoup.org", res.header("Location")); } @Test @@ -547,7 +547,7 @@ public void postJpeg() throws IOException { @Test public void handles201Created() throws IOException { Document doc = Jsoup.connect("http://direct.infohound.net/tools/201.pl").get(); // 201, location=jsoup - assertEquals("https://jsoup.org", doc.location()); + assertEquals("https://jsoup.org/", doc.location()); } @Test @@ -661,4 +661,37 @@ public void throwsIfRequestBodyForGet() throws IOException { } assertTrue(caught); } + + @Test + public void canSpecifyResponseCharset() throws IOException { + // both these docs have <80> in there as euro/control char depending on charset + String noCharsetUrl = "http://direct.infohound.net/tools/Windows-1252-nocharset.html"; + String charsetUrl = "http://direct.infohound.net/tools/Windows-1252-charset.html"; + + // included in meta + Connection.Response res1 = Jsoup.connect(charsetUrl).execute(); + assertEquals(null, res1.charset()); // not set in headers + final Document doc1 = res1.parse(); + assertEquals("windows-1252", doc1.charset().displayName()); // but determined at parse time + assertEquals("Cost is €100", doc1.select("p").text()); + assertTrue(res1.body().contains("€")); + + // no meta, no override + Connection.Response res2 = Jsoup.connect(noCharsetUrl).execute(); + assertEquals(null, res2.charset()); // not set in headers + final Document doc2 = res2.parse(); + assertEquals("UTF-8", doc2.charset().displayName()); // so defaults to utf-8 + assertEquals("Cost is �100", doc2.select("p").text()); + assertTrue(res2.body().contains("�")); + + // no meta, let's override + Connection.Response res3 = Jsoup.connect(noCharsetUrl).execute(); + assertEquals(null, res3.charset()); // not set in headers + res3.charset("windows-1252"); + assertEquals("windows-1252", res3.charset()); // read back + final Document doc3 = res3.parse(); + assertEquals("windows-1252", doc3.charset().displayName()); // from override + assertEquals("Cost is €100", doc3.select("p").text()); + assertTrue(res3.body().contains("€")); + } } From aa81e10c34f48a3c4ac7160aa90ee18af4f5c0c2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 19 Aug 2016 12:04:21 -0700 Subject: [PATCH 034/774] Improved HTML entity support Fixes #602, #603 --- CHANGES | 5 + pom.xml | 8 + src/main/java/org/jsoup/nodes/Entities.java | 201 +- .../org/jsoup/nodes/entities-base.properties | 212 +- .../org/jsoup/nodes/entities-full.properties | 4157 +++++++++-------- .../org/jsoup/nodes/entities-xhtml.properties | 4 + src/main/java/org/jsoup/parser/Token.java | 7 + src/main/java/org/jsoup/parser/Tokeniser.java | 44 +- .../java/org/jsoup/parser/TokeniserState.java | 8 +- .../org/jsoup/integration/UrlConnectTest.java | 2 +- .../java/org/jsoup/nodes/BuildEntities.java | 136 + .../java/org/jsoup/nodes/EntitiesTest.java | 55 +- .../java/org/jsoup/safety/CleanerTest.java | 2 +- 13 files changed, 2604 insertions(+), 2237 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/entities-xhtml.properties create mode 100644 src/test/java/org/jsoup/nodes/BuildEntities.java diff --git a/CHANGES b/CHANGES index 6a91bdd617..2818690ee9 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,11 @@ jsoup changelog case, according to the relevant spec. The CSS selectors for tags and attributes remain case insensitive, per the CSS spec. + * Improved support for extended HTML entities, including supplemental characters and multiple character references. + Also reduced memory consumption of the entity tables. + + + * Added support for *|E wildcard namespace selectors. diff --git a/pom.xml b/pom.xml index 4726b7fa2a..445788c805 100644 --- a/pom.xml +++ b/pom.xml @@ -199,6 +199,14 @@ test + + + com.google.code.gson + gson + 2.7 + test + + diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 7eff75d3a7..1a9144fef0 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -4,10 +4,18 @@ import org.jsoup.helper.StringUtil; import org.jsoup.parser.Parser; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.charset.CharsetEncoder; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.jsoup.nodes.Entities.EscapeMode.base; +import static org.jsoup.nodes.Entities.EscapeMode.extended; /** * HTML entities, and escape routines. @@ -15,40 +23,64 @@ * named character references. */ public class Entities { + private static Pattern entityPattern = Pattern.compile("^(\\w+)=(\\w+)(?:,(\\w+))?;(\\w+)$"); + static final int empty = -1; + static final String emptyName = ""; + static final int codepointRadix = 36; + public enum EscapeMode { /** Restricted entities suitable for XHTML output: lt, gt, amp, and quot only. */ - xhtml(xhtmlByVal), + xhtml("entities-xhtml.properties", 4), /** Default HTML output entities. */ - base(baseByVal), + base("entities-base.properties", 106), /** Complete HTML entities. */ - extended(fullByVal); + extended("entities-full.properties", 2125); + + // table of named references to their codepoints. sorted so we can binary search. built by BuildEntities. + private String[] nameKeys; + private int[] codeVals; // limitation is the few references with multiple characters; those go into multipoints. + + // table of codepoints to named entities. + private int[] codeKeys; // we don' support multicodepoints to single named value currently + private String[] nameVals; - private Map map; + EscapeMode(String file, int size) { + load(this, file, size); + } + + int codepointForName(final String name) { + int index = Arrays.binarySearch(nameKeys, name); + return index >= 0 ? codeVals[index] : empty; + } - EscapeMode(Map map) { - this.map = map; + String nameForCodepoint(final int codepoint) { + final int index = Arrays.binarySearch(codeKeys, codepoint); + if (index >= 0) { + // the results are ordered so lower case versions of same codepoint come after uppercase, and we prefer to emit lower + // (and binary search for same item with multi results is undefined + return (index < nameVals.length-1 && codeKeys[index+1] == codepoint) ? + nameVals[index+1] : nameVals[index]; + } + return emptyName; } - public Map getMap() { - return map; + private int size() { + return nameKeys.length; } } - private static final Map full; - private static final Map xhtmlByVal; - private static final Map base; - private static final Map baseByVal; - private static final Map fullByVal; + private static final HashMap multipoints = new HashMap(); // name -> multiple character references - private Entities() {} + private Entities() { + } /** * Check if the input is a known named entity * @param name the possible entity name (e.g. "lt" or "amp") * @return true if a known named entity */ - public static boolean isNamedEntity(String name) { - return full.containsKey(name); + public static boolean isNamedEntity(final String name) { + return extended.codepointForName(name) != empty; } /** @@ -57,19 +89,50 @@ public static boolean isNamedEntity(String name) { * @return true if a known named entity in the base set * @see #isNamedEntity(String) */ - public static boolean isBaseNamedEntity(String name) { - return base.containsKey(name); + public static boolean isBaseNamedEntity(final String name) { + return base.codepointForName(name) != empty; } /** * Get the Character value of the named entity * @param name named entity (e.g. "lt" or "amp") * @return the Character value of the named entity (e.g. '{@literal <}' or '{@literal &}') + * @deprecated does not support characters outside the BMP or multiple character names */ public static Character getCharacterByName(String name) { - return full.get(name); + return (char) extended.codepointForName(name); + } + + /** + * Get the character(s) represented by the named entitiy + * @param name entity (e.g. "lt" or "amp") + * @return the string value of the character(s) represented by this entity, or "" if not defined + */ + public static String getByName(String name) { + String val = multipoints.get(name); + if (val != null) + return val; + int codepoint = extended.codepointForName(name); + if (codepoint != empty) + return new String(new int[]{codepoint}, 0, 1); + return emptyName; + } + + public static int codepointsForName(final String name, final int[] codepoints) { + String val = multipoints.get(name); + if (val != null) { + codepoints[0] = val.codePointAt(0); + codepoints[1] = val.codePointAt(1); + return 2; + } + int codepoint = extended.codepointForName(name); + if (codepoint != empty) { + codepoints[0] = codepoint; + return 1; + } + return 0; } - + static String escape(String string, Document.OutputSettings out) { StringBuilder accum = new StringBuilder(string.length() * 2); try { @@ -89,7 +152,6 @@ static void escape(Appendable accum, String string, Document.OutputSettings out, final EscapeMode escapeMode = out.escapeMode(); final CharsetEncoder encoder = out.encoder(); final CoreCharset coreCharset = CoreCharset.byName(encoder.charset().name()); - final Map map = escapeMode.getMap(); final int length = string.length(); int codePoint; @@ -144,21 +206,27 @@ static void escape(Appendable accum, String string, Document.OutputSettings out, default: if (canEncode(coreCharset, c, encoder)) accum.append(c); - else if (map.containsKey(c)) - accum.append('&').append(map.get(c)).append(';'); else - accum.append("&#x").append(Integer.toHexString(codePoint)).append(';'); + appendEncoded(accum, escapeMode, codePoint); } } else { final String c = new String(Character.toChars(codePoint)); if (encoder.canEncode(c)) // uses fallback encoder for simplicity accum.append(c); else - accum.append("&#x").append(Integer.toHexString(codePoint)).append(';'); + appendEncoded(accum, escapeMode, codePoint); } } } + private static void appendEncoded(Appendable accum, EscapeMode escapeMode, int codePoint) throws IOException { + final String name = escapeMode.nameForCodepoint(codePoint); + if (name != emptyName) // ok for identity check + accum.append('&').append(name).append(';'); + else + accum.append("&#x").append(Integer.toHexString(codePoint)).append(';'); + } + static String unescape(String string) { return unescape(string, false); } @@ -186,7 +254,6 @@ static String unescape(String string, boolean strict) { * Alterslash: 3013, 28 * Jsoup: 167, 2 */ - private static boolean canEncode(final CoreCharset charset, final char c, final CharsetEncoder fallback) { // todo add more charset tests if impacted by Android's bad perf in canEncode switch (charset) { @@ -211,61 +278,43 @@ private static CoreCharset byName(String name) { } } + private static void load(EscapeMode e, String file, int size) { + e.nameKeys = new String[size]; + e.codeVals = new int[size]; + e.codeKeys = new int[size]; + e.nameVals = new String[size]; - // xhtml has restricted entities - private static final Object[][] xhtmlArray = { - {"quot", 0x00022}, - {"amp", 0x00026}, - {"lt", 0x0003C}, - {"gt", 0x0003E} - }; - - static { - xhtmlByVal = new HashMap(); - base = loadEntities("entities-base.properties"); // most common / default - baseByVal = toCharacterKey(base); - full = loadEntities("entities-full.properties"); // extended and overblown. - fullByVal = toCharacterKey(full); - - for (Object[] entity : xhtmlArray) { - Character c = Character.valueOf((char) ((Integer) entity[1]).intValue()); - xhtmlByVal.put(c, ((String) entity[0])); - } - } - - private static Map loadEntities(String filename) { - Properties properties = new Properties(); - Map entities = new HashMap(); + InputStream stream = Entities.class.getResourceAsStream(file); + if (stream == null) + throw new IllegalStateException("Could not read resource " + file + ". Make sure you copy resources for " + Entities.class.getCanonicalName()); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + String entry; + int i = 0; try { - InputStream in = Entities.class.getResourceAsStream(filename); - properties.load(in); - in.close(); - } catch (IOException e) { - throw new MissingResourceException("Error loading entities resource: " + e.getMessage(), "Entities", filename); - } + while ((entry = reader.readLine()) != null) { + // NotNestedLessLess=10913,824;1887 + final Matcher match = entityPattern.matcher(entry); + if (match.find()) { + final String name = match.group(1); + final int cp1 = Integer.parseInt(match.group(2), codepointRadix); + final int cp2 = match.group(3) != null ? Integer.parseInt(match.group(3), codepointRadix) : empty; + final int index = Integer.parseInt(match.group(4), codepointRadix); - for (Map.Entry entry: properties.entrySet()) { - Character val = Character.valueOf((char) Integer.parseInt((String) entry.getValue(), 16)); - String name = (String) entry.getKey(); - entities.put(name, val); - } - return entities; - } + e.nameKeys[i] = name; + e.codeVals[i] = cp1; + e.codeKeys[index] = cp1; + e.nameVals[index] = name; - private static Map toCharacterKey(Map inMap) { - Map outMap = new HashMap(); - for (Map.Entry entry: inMap.entrySet()) { - Character character = entry.getValue(); - String name = entry.getKey(); + if (cp2 != empty) { + multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); + } + i++; + } - if (outMap.containsKey(character)) { - // dupe, prefer the lower case version - if (name.toLowerCase().equals(name)) - outMap.put(character, name); - } else { - outMap.put(character, name); } + reader.close(); + } catch (IOException err) { + throw new IllegalStateException("Error reading resource " + file); } - return outMap; } } diff --git a/src/main/java/org/jsoup/nodes/entities-base.properties b/src/main/java/org/jsoup/nodes/entities-base.properties index 3d1d11e6c4..465bd33b57 100644 --- a/src/main/java/org/jsoup/nodes/entities-base.properties +++ b/src/main/java/org/jsoup/nodes/entities-base.properties @@ -1,106 +1,106 @@ -AElig=000C6 -AMP=00026 -Aacute=000C1 -Acirc=000C2 -Agrave=000C0 -Aring=000C5 -Atilde=000C3 -Auml=000C4 -COPY=000A9 -Ccedil=000C7 -ETH=000D0 -Eacute=000C9 -Ecirc=000CA -Egrave=000C8 -Euml=000CB -GT=0003E -Iacute=000CD -Icirc=000CE -Igrave=000CC -Iuml=000CF -LT=0003C -Ntilde=000D1 -Oacute=000D3 -Ocirc=000D4 -Ograve=000D2 -Oslash=000D8 -Otilde=000D5 -Ouml=000D6 -QUOT=00022 -REG=000AE -THORN=000DE -Uacute=000DA -Ucirc=000DB -Ugrave=000D9 -Uuml=000DC -Yacute=000DD -aacute=000E1 -acirc=000E2 -acute=000B4 -aelig=000E6 -agrave=000E0 -amp=00026 -aring=000E5 -atilde=000E3 -auml=000E4 -brvbar=000A6 -ccedil=000E7 -cedil=000B8 -cent=000A2 -copy=000A9 -curren=000A4 -deg=000B0 -divide=000F7 -eacute=000E9 -ecirc=000EA -egrave=000E8 -eth=000F0 -euml=000EB -frac12=000BD -frac14=000BC -frac34=000BE -gt=0003E -iacute=000ED -icirc=000EE -iexcl=000A1 -igrave=000EC -iquest=000BF -iuml=000EF -laquo=000AB -lt=0003C -macr=000AF -micro=000B5 -middot=000B7 -nbsp=000A0 -not=000AC -ntilde=000F1 -oacute=000F3 -ocirc=000F4 -ograve=000F2 -ordf=000AA -ordm=000BA -oslash=000F8 -otilde=000F5 -ouml=000F6 -para=000B6 -plusmn=000B1 -pound=000A3 -quot=00022 -raquo=000BB -reg=000AE -sect=000A7 -shy=000AD -sup1=000B9 -sup2=000B2 -sup3=000B3 -szlig=000DF -thorn=000FE -times=000D7 -uacute=000FA -ucirc=000FB -ugrave=000F9 -uml=000A8 -uuml=000FC -yacute=000FD -yen=000A5 -yuml=000FF +AElig=5i;1c +AMP=12;2 +Aacute=5d;17 +Acirc=5e;18 +Agrave=5c;16 +Aring=5h;1b +Atilde=5f;19 +Auml=5g;1a +COPY=4p;h +Ccedil=5j;1d +ETH=5s;1m +Eacute=5l;1f +Ecirc=5m;1g +Egrave=5k;1e +Euml=5n;1h +GT=1q;6 +Iacute=5p;1j +Icirc=5q;1k +Igrave=5o;1i +Iuml=5r;1l +LT=1o;4 +Ntilde=5t;1n +Oacute=5v;1p +Ocirc=5w;1q +Ograve=5u;1o +Oslash=60;1u +Otilde=5x;1r +Ouml=5y;1s +QUOT=y;0 +REG=4u;n +THORN=66;20 +Uacute=62;1w +Ucirc=63;1x +Ugrave=61;1v +Uuml=64;1y +Yacute=65;1z +aacute=69;23 +acirc=6a;24 +acute=50;u +aelig=6e;28 +agrave=68;22 +amp=12;3 +aring=6d;27 +atilde=6b;25 +auml=6c;26 +brvbar=4m;e +ccedil=6f;29 +cedil=54;y +cent=4i;a +copy=4p;i +curren=4k;c +deg=4w;q +divide=6v;2p +eacute=6h;2b +ecirc=6i;2c +egrave=6g;2a +eth=6o;2i +euml=6j;2d +frac12=59;13 +frac14=58;12 +frac34=5a;14 +gt=1q;7 +iacute=6l;2f +icirc=6m;2g +iexcl=4h;9 +igrave=6k;2e +iquest=5b;15 +iuml=6n;2h +laquo=4r;k +lt=1o;5 +macr=4v;p +micro=51;v +middot=53;x +nbsp=4g;8 +not=4s;l +ntilde=6p;2j +oacute=6r;2l +ocirc=6s;2m +ograve=6q;2k +ordf=4q;j +ordm=56;10 +oslash=6w;2q +otilde=6t;2n +ouml=6u;2o +para=52;w +plusmn=4x;r +pound=4j;b +quot=y;1 +raquo=57;11 +reg=4u;o +sect=4n;f +shy=4t;m +sup1=55;z +sup2=4y;s +sup3=4z;t +szlig=67;21 +thorn=72;2w +times=5z;1t +uacute=6y;2s +ucirc=6z;2t +ugrave=6x;2r +uml=4o;g +uuml=70;2u +yacute=71;2v +yen=4l;d +yuml=73;2x diff --git a/src/main/java/org/jsoup/nodes/entities-full.properties b/src/main/java/org/jsoup/nodes/entities-full.properties index 92f124f408..76eadf2a69 100644 --- a/src/main/java/org/jsoup/nodes/entities-full.properties +++ b/src/main/java/org/jsoup/nodes/entities-full.properties @@ -1,2032 +1,2125 @@ -AElig=000C6 -AMP=00026 -Aacute=000C1 -Abreve=00102 -Acirc=000C2 -Acy=00410 -Afr=1D504 -Agrave=000C0 -Alpha=00391 -Amacr=00100 -And=02A53 -Aogon=00104 -Aopf=1D538 -ApplyFunction=02061 -Aring=000C5 -Ascr=1D49C -Assign=02254 -Atilde=000C3 -Auml=000C4 -Backslash=02216 -Barv=02AE7 -Barwed=02306 -Bcy=00411 -Because=02235 -Bernoullis=0212C -Beta=00392 -Bfr=1D505 -Bopf=1D539 -Breve=002D8 -Bscr=0212C -Bumpeq=0224E -CHcy=00427 -COPY=000A9 -Cacute=00106 -Cap=022D2 -CapitalDifferentialD=02145 -Cayleys=0212D -Ccaron=0010C -Ccedil=000C7 -Ccirc=00108 -Cconint=02230 -Cdot=0010A -Cedilla=000B8 -CenterDot=000B7 -Cfr=0212D -Chi=003A7 -CircleDot=02299 -CircleMinus=02296 -CirclePlus=02295 -CircleTimes=02297 -ClockwiseContourIntegral=02232 -CloseCurlyDoubleQuote=0201D -CloseCurlyQuote=02019 -Colon=02237 -Colone=02A74 -Congruent=02261 -Conint=0222F -ContourIntegral=0222E -Copf=02102 -Coproduct=02210 -CounterClockwiseContourIntegral=02233 -Cross=02A2F -Cscr=1D49E -Cup=022D3 -CupCap=0224D -DD=02145 -DDotrahd=02911 -DJcy=00402 -DScy=00405 -DZcy=0040F -Dagger=02021 -Darr=021A1 -Dashv=02AE4 -Dcaron=0010E -Dcy=00414 -Del=02207 -Delta=00394 -Dfr=1D507 -DiacriticalAcute=000B4 -DiacriticalDot=002D9 -DiacriticalDoubleAcute=002DD -DiacriticalGrave=00060 -DiacriticalTilde=002DC -Diamond=022C4 -DifferentialD=02146 -Dopf=1D53B -Dot=000A8 -DotDot=020DC -DotEqual=02250 -DoubleContourIntegral=0222F -DoubleDot=000A8 -DoubleDownArrow=021D3 -DoubleLeftArrow=021D0 -DoubleLeftRightArrow=021D4 -DoubleLeftTee=02AE4 -DoubleLongLeftArrow=027F8 -DoubleLongLeftRightArrow=027FA -DoubleLongRightArrow=027F9 -DoubleRightArrow=021D2 -DoubleRightTee=022A8 -DoubleUpArrow=021D1 -DoubleUpDownArrow=021D5 -DoubleVerticalBar=02225 -DownArrow=02193 -DownArrowBar=02913 -DownArrowUpArrow=021F5 -DownBreve=00311 -DownLeftRightVector=02950 -DownLeftTeeVector=0295E -DownLeftVector=021BD -DownLeftVectorBar=02956 -DownRightTeeVector=0295F -DownRightVector=021C1 -DownRightVectorBar=02957 -DownTee=022A4 -DownTeeArrow=021A7 -Downarrow=021D3 -Dscr=1D49F -Dstrok=00110 -ENG=0014A -ETH=000D0 -Eacute=000C9 -Ecaron=0011A -Ecirc=000CA -Ecy=0042D -Edot=00116 -Efr=1D508 -Egrave=000C8 -Element=02208 -Emacr=00112 -EmptySmallSquare=025FB -EmptyVerySmallSquare=025AB -Eogon=00118 -Eopf=1D53C -Epsilon=00395 -Equal=02A75 -EqualTilde=02242 -Equilibrium=021CC -Escr=02130 -Esim=02A73 -Eta=00397 -Euml=000CB -Exists=02203 -ExponentialE=02147 -Fcy=00424 -Ffr=1D509 -FilledSmallSquare=025FC -FilledVerySmallSquare=025AA -Fopf=1D53D -ForAll=02200 -Fouriertrf=02131 -Fscr=02131 -GJcy=00403 -GT=0003E -Gamma=00393 -Gammad=003DC -Gbreve=0011E -Gcedil=00122 -Gcirc=0011C -Gcy=00413 -Gdot=00120 -Gfr=1D50A -Gg=022D9 -Gopf=1D53E -GreaterEqual=02265 -GreaterEqualLess=022DB -GreaterFullEqual=02267 -GreaterGreater=02AA2 -GreaterLess=02277 -GreaterSlantEqual=02A7E -GreaterTilde=02273 -Gscr=1D4A2 -Gt=0226B -HARDcy=0042A -Hacek=002C7 -Hat=0005E -Hcirc=00124 -Hfr=0210C -HilbertSpace=0210B -Hopf=0210D -HorizontalLine=02500 -Hscr=0210B -Hstrok=00126 -HumpDownHump=0224E -HumpEqual=0224F -IEcy=00415 -IJlig=00132 -IOcy=00401 -Iacute=000CD -Icirc=000CE -Icy=00418 -Idot=00130 -Ifr=02111 -Igrave=000CC -Im=02111 -Imacr=0012A -ImaginaryI=02148 -Implies=021D2 -Int=0222C -Integral=0222B -Intersection=022C2 -InvisibleComma=02063 -InvisibleTimes=02062 -Iogon=0012E -Iopf=1D540 -Iota=00399 -Iscr=02110 -Itilde=00128 -Iukcy=00406 -Iuml=000CF -Jcirc=00134 -Jcy=00419 -Jfr=1D50D -Jopf=1D541 -Jscr=1D4A5 -Jsercy=00408 -Jukcy=00404 -KHcy=00425 -KJcy=0040C -Kappa=0039A -Kcedil=00136 -Kcy=0041A -Kfr=1D50E -Kopf=1D542 -Kscr=1D4A6 -LJcy=00409 -LT=0003C -Lacute=00139 -Lambda=0039B -Lang=027EA -Laplacetrf=02112 -Larr=0219E -Lcaron=0013D -Lcedil=0013B -Lcy=0041B -LeftAngleBracket=027E8 -LeftArrow=02190 -LeftArrowBar=021E4 -LeftArrowRightArrow=021C6 -LeftCeiling=02308 -LeftDoubleBracket=027E6 -LeftDownTeeVector=02961 -LeftDownVector=021C3 -LeftDownVectorBar=02959 -LeftFloor=0230A -LeftRightArrow=02194 -LeftRightVector=0294E -LeftTee=022A3 -LeftTeeArrow=021A4 -LeftTeeVector=0295A -LeftTriangle=022B2 -LeftTriangleBar=029CF -LeftTriangleEqual=022B4 -LeftUpDownVector=02951 -LeftUpTeeVector=02960 -LeftUpVector=021BF -LeftUpVectorBar=02958 -LeftVector=021BC -LeftVectorBar=02952 -Leftarrow=021D0 -Leftrightarrow=021D4 -LessEqualGreater=022DA -LessFullEqual=02266 -LessGreater=02276 -LessLess=02AA1 -LessSlantEqual=02A7D -LessTilde=02272 -Lfr=1D50F -Ll=022D8 -Lleftarrow=021DA -Lmidot=0013F -LongLeftArrow=027F5 -LongLeftRightArrow=027F7 -LongRightArrow=027F6 -Longleftarrow=027F8 -Longleftrightarrow=027FA -Longrightarrow=027F9 -Lopf=1D543 -LowerLeftArrow=02199 -LowerRightArrow=02198 -Lscr=02112 -Lsh=021B0 -Lstrok=00141 -Lt=0226A -Map=02905 -Mcy=0041C -MediumSpace=0205F -Mellintrf=02133 -Mfr=1D510 -MinusPlus=02213 -Mopf=1D544 -Mscr=02133 -Mu=0039C -NJcy=0040A -Nacute=00143 -Ncaron=00147 -Ncedil=00145 -Ncy=0041D -NegativeMediumSpace=0200B -NegativeThickSpace=0200B -NegativeThinSpace=0200B -NegativeVeryThinSpace=0200B -NestedGreaterGreater=0226B -NestedLessLess=0226A -NewLine=0000A -Nfr=1D511 -NoBreak=02060 -NonBreakingSpace=000A0 -Nopf=02115 -Not=02AEC -NotCongruent=02262 -NotCupCap=0226D -NotDoubleVerticalBar=02226 -NotElement=02209 -NotEqual=02260 -NotExists=02204 -NotGreater=0226F -NotGreaterEqual=02271 -NotGreaterLess=02279 -NotGreaterTilde=02275 -NotLeftTriangle=022EA -NotLeftTriangleEqual=022EC -NotLess=0226E -NotLessEqual=02270 -NotLessGreater=02278 -NotLessTilde=02274 -NotPrecedes=02280 -NotPrecedesSlantEqual=022E0 -NotReverseElement=0220C -NotRightTriangle=022EB -NotRightTriangleEqual=022ED -NotSquareSubsetEqual=022E2 -NotSquareSupersetEqual=022E3 -NotSubsetEqual=02288 -NotSucceeds=02281 -NotSucceedsSlantEqual=022E1 -NotSupersetEqual=02289 -NotTilde=02241 -NotTildeEqual=02244 -NotTildeFullEqual=02247 -NotTildeTilde=02249 -NotVerticalBar=02224 -Nscr=1D4A9 -Ntilde=000D1 -Nu=0039D -OElig=00152 -Oacute=000D3 -Ocirc=000D4 -Ocy=0041E -Odblac=00150 -Ofr=1D512 -Ograve=000D2 -Omacr=0014C -Omega=003A9 -Omicron=0039F -Oopf=1D546 -OpenCurlyDoubleQuote=0201C -OpenCurlyQuote=02018 -Or=02A54 -Oscr=1D4AA -Oslash=000D8 -Otilde=000D5 -Otimes=02A37 -Ouml=000D6 -OverBar=0203E -OverBrace=023DE -OverBracket=023B4 -OverParenthesis=023DC -PartialD=02202 -Pcy=0041F -Pfr=1D513 -Phi=003A6 -Pi=003A0 -PlusMinus=000B1 -Poincareplane=0210C -Popf=02119 -Pr=02ABB -Precedes=0227A -PrecedesEqual=02AAF -PrecedesSlantEqual=0227C -PrecedesTilde=0227E -Prime=02033 -Product=0220F -Proportion=02237 -Proportional=0221D -Pscr=1D4AB -Psi=003A8 -QUOT=00022 -Qfr=1D514 -Qopf=0211A -Qscr=1D4AC -RBarr=02910 -REG=000AE -Racute=00154 -Rang=027EB -Rarr=021A0 -Rarrtl=02916 -Rcaron=00158 -Rcedil=00156 -Rcy=00420 -Re=0211C -ReverseElement=0220B -ReverseEquilibrium=021CB -ReverseUpEquilibrium=0296F -Rfr=0211C -Rho=003A1 -RightAngleBracket=027E9 -RightArrow=02192 -RightArrowBar=021E5 -RightArrowLeftArrow=021C4 -RightCeiling=02309 -RightDoubleBracket=027E7 -RightDownTeeVector=0295D -RightDownVector=021C2 -RightDownVectorBar=02955 -RightFloor=0230B -RightTee=022A2 -RightTeeArrow=021A6 -RightTeeVector=0295B -RightTriangle=022B3 -RightTriangleBar=029D0 -RightTriangleEqual=022B5 -RightUpDownVector=0294F -RightUpTeeVector=0295C -RightUpVector=021BE -RightUpVectorBar=02954 -RightVector=021C0 -RightVectorBar=02953 -Rightarrow=021D2 -Ropf=0211D -RoundImplies=02970 -Rrightarrow=021DB -Rscr=0211B -Rsh=021B1 -RuleDelayed=029F4 -SHCHcy=00429 -SHcy=00428 -SOFTcy=0042C -Sacute=0015A -Sc=02ABC -Scaron=00160 -Scedil=0015E -Scirc=0015C -Scy=00421 -Sfr=1D516 -ShortDownArrow=02193 -ShortLeftArrow=02190 -ShortRightArrow=02192 -ShortUpArrow=02191 -Sigma=003A3 -SmallCircle=02218 -Sopf=1D54A -Sqrt=0221A -Square=025A1 -SquareIntersection=02293 -SquareSubset=0228F -SquareSubsetEqual=02291 -SquareSuperset=02290 -SquareSupersetEqual=02292 -SquareUnion=02294 -Sscr=1D4AE -Star=022C6 -Sub=022D0 -Subset=022D0 -SubsetEqual=02286 -Succeeds=0227B -SucceedsEqual=02AB0 -SucceedsSlantEqual=0227D -SucceedsTilde=0227F -SuchThat=0220B -Sum=02211 -Sup=022D1 -Superset=02283 -SupersetEqual=02287 -Supset=022D1 -THORN=000DE -TRADE=02122 -TSHcy=0040B -TScy=00426 -Tab=00009 -Tau=003A4 -Tcaron=00164 -Tcedil=00162 -Tcy=00422 -Tfr=1D517 -Therefore=02234 -Theta=00398 -ThinSpace=02009 -Tilde=0223C -TildeEqual=02243 -TildeFullEqual=02245 -TildeTilde=02248 -Topf=1D54B -TripleDot=020DB -Tscr=1D4AF -Tstrok=00166 -Uacute=000DA -Uarr=0219F -Uarrocir=02949 -Ubrcy=0040E -Ubreve=0016C -Ucirc=000DB -Ucy=00423 -Udblac=00170 -Ufr=1D518 -Ugrave=000D9 -Umacr=0016A -UnderBar=0005F -UnderBrace=023DF -UnderBracket=023B5 -UnderParenthesis=023DD -Union=022C3 -UnionPlus=0228E -Uogon=00172 -Uopf=1D54C -UpArrow=02191 -UpArrowBar=02912 -UpArrowDownArrow=021C5 -UpDownArrow=02195 -UpEquilibrium=0296E -UpTee=022A5 -UpTeeArrow=021A5 -Uparrow=021D1 -Updownarrow=021D5 -UpperLeftArrow=02196 -UpperRightArrow=02197 -Upsi=003D2 -Upsilon=003A5 -Uring=0016E -Uscr=1D4B0 -Utilde=00168 -Uuml=000DC -VDash=022AB -Vbar=02AEB -Vcy=00412 -Vdash=022A9 -Vdashl=02AE6 -Vee=022C1 -Verbar=02016 -Vert=02016 -VerticalBar=02223 -VerticalLine=0007C -VerticalSeparator=02758 -VerticalTilde=02240 -VeryThinSpace=0200A -Vfr=1D519 -Vopf=1D54D -Vscr=1D4B1 -Vvdash=022AA -Wcirc=00174 -Wedge=022C0 -Wfr=1D51A -Wopf=1D54E -Wscr=1D4B2 -Xfr=1D51B -Xi=0039E -Xopf=1D54F -Xscr=1D4B3 -YAcy=0042F -YIcy=00407 -YUcy=0042E -Yacute=000DD -Ycirc=00176 -Ycy=0042B -Yfr=1D51C -Yopf=1D550 -Yscr=1D4B4 -Yuml=00178 -ZHcy=00416 -Zacute=00179 -Zcaron=0017D -Zcy=00417 -Zdot=0017B -ZeroWidthSpace=0200B -Zeta=00396 -Zfr=02128 -Zopf=02124 -Zscr=1D4B5 -aacute=000E1 -abreve=00103 -ac=0223E -acd=0223F -acirc=000E2 -acute=000B4 -acy=00430 -aelig=000E6 -af=02061 -afr=1D51E -agrave=000E0 -alefsym=02135 -aleph=02135 -alpha=003B1 -amacr=00101 -amalg=02A3F -amp=00026 -and=02227 -andand=02A55 -andd=02A5C -andslope=02A58 -andv=02A5A -ang=02220 -ange=029A4 -angle=02220 -angmsd=02221 -angmsdaa=029A8 -angmsdab=029A9 -angmsdac=029AA -angmsdad=029AB -angmsdae=029AC -angmsdaf=029AD -angmsdag=029AE -angmsdah=029AF -angrt=0221F -angrtvb=022BE -angrtvbd=0299D -angsph=02222 -angst=000C5 -angzarr=0237C -aogon=00105 -aopf=1D552 -ap=02248 -apE=02A70 -apacir=02A6F -ape=0224A -apid=0224B -apos=00027 -approx=02248 -approxeq=0224A -aring=000E5 -ascr=1D4B6 -ast=0002A -asymp=02248 -asympeq=0224D -atilde=000E3 -auml=000E4 -awconint=02233 -awint=02A11 -bNot=02AED -backcong=0224C -backepsilon=003F6 -backprime=02035 -backsim=0223D -backsimeq=022CD -barvee=022BD -barwed=02305 -barwedge=02305 -bbrk=023B5 -bbrktbrk=023B6 -bcong=0224C -bcy=00431 -bdquo=0201E -becaus=02235 -because=02235 -bemptyv=029B0 -bepsi=003F6 -bernou=0212C -beta=003B2 -beth=02136 -between=0226C -bfr=1D51F -bigcap=022C2 -bigcirc=025EF -bigcup=022C3 -bigodot=02A00 -bigoplus=02A01 -bigotimes=02A02 -bigsqcup=02A06 -bigstar=02605 -bigtriangledown=025BD -bigtriangleup=025B3 -biguplus=02A04 -bigvee=022C1 -bigwedge=022C0 -bkarow=0290D -blacklozenge=029EB -blacksquare=025AA -blacktriangle=025B4 -blacktriangledown=025BE -blacktriangleleft=025C2 -blacktriangleright=025B8 -blank=02423 -blk12=02592 -blk14=02591 -blk34=02593 -block=02588 -bnot=02310 -bopf=1D553 -bot=022A5 -bottom=022A5 -bowtie=022C8 -boxDL=02557 -boxDR=02554 -boxDl=02556 -boxDr=02553 -boxH=02550 -boxHD=02566 -boxHU=02569 -boxHd=02564 -boxHu=02567 -boxUL=0255D -boxUR=0255A -boxUl=0255C -boxUr=02559 -boxV=02551 -boxVH=0256C -boxVL=02563 -boxVR=02560 -boxVh=0256B -boxVl=02562 -boxVr=0255F -boxbox=029C9 -boxdL=02555 -boxdR=02552 -boxdl=02510 -boxdr=0250C -boxh=02500 -boxhD=02565 -boxhU=02568 -boxhd=0252C -boxhu=02534 -boxminus=0229F -boxplus=0229E -boxtimes=022A0 -boxuL=0255B -boxuR=02558 -boxul=02518 -boxur=02514 -boxv=02502 -boxvH=0256A -boxvL=02561 -boxvR=0255E -boxvh=0253C -boxvl=02524 -boxvr=0251C -bprime=02035 -breve=002D8 -brvbar=000A6 -bscr=1D4B7 -bsemi=0204F -bsim=0223D -bsime=022CD -bsol=0005C -bsolb=029C5 -bsolhsub=027C8 -bull=02022 -bullet=02022 -bump=0224E -bumpE=02AAE -bumpe=0224F -bumpeq=0224F -cacute=00107 -cap=02229 -capand=02A44 -capbrcup=02A49 -capcap=02A4B -capcup=02A47 -capdot=02A40 -caret=02041 -caron=002C7 -ccaps=02A4D -ccaron=0010D -ccedil=000E7 -ccirc=00109 -ccups=02A4C -ccupssm=02A50 -cdot=0010B -cedil=000B8 -cemptyv=029B2 -cent=000A2 -centerdot=000B7 -cfr=1D520 -chcy=00447 -check=02713 -checkmark=02713 -chi=003C7 -cir=025CB -cirE=029C3 -circ=002C6 -circeq=02257 -circlearrowleft=021BA -circlearrowright=021BB -circledR=000AE -circledS=024C8 -circledast=0229B -circledcirc=0229A -circleddash=0229D -cire=02257 -cirfnint=02A10 -cirmid=02AEF -cirscir=029C2 -clubs=02663 -clubsuit=02663 -colon=0003A -colone=02254 -coloneq=02254 -comma=0002C -commat=00040 -comp=02201 -compfn=02218 -complement=02201 -complexes=02102 -cong=02245 -congdot=02A6D -conint=0222E -copf=1D554 -coprod=02210 -copy=000A9 -copysr=02117 -crarr=021B5 -cross=02717 -cscr=1D4B8 -csub=02ACF -csube=02AD1 -csup=02AD0 -csupe=02AD2 -ctdot=022EF -cudarrl=02938 -cudarrr=02935 -cuepr=022DE -cuesc=022DF -cularr=021B6 -cularrp=0293D -cup=0222A -cupbrcap=02A48 -cupcap=02A46 -cupcup=02A4A -cupdot=0228D -cupor=02A45 -curarr=021B7 -curarrm=0293C -curlyeqprec=022DE -curlyeqsucc=022DF -curlyvee=022CE -curlywedge=022CF -curren=000A4 -curvearrowleft=021B6 -curvearrowright=021B7 -cuvee=022CE -cuwed=022CF -cwconint=02232 -cwint=02231 -cylcty=0232D -dArr=021D3 -dHar=02965 -dagger=02020 -daleth=02138 -darr=02193 -dash=02010 -dashv=022A3 -dbkarow=0290F -dblac=002DD -dcaron=0010F -dcy=00434 -dd=02146 -ddagger=02021 -ddarr=021CA -ddotseq=02A77 -deg=000B0 -delta=003B4 -demptyv=029B1 -dfisht=0297F -dfr=1D521 -dharl=021C3 -dharr=021C2 -diam=022C4 -diamond=022C4 -diamondsuit=02666 -diams=02666 -die=000A8 -digamma=003DD -disin=022F2 -div=000F7 -divide=000F7 -divideontimes=022C7 -divonx=022C7 -djcy=00452 -dlcorn=0231E -dlcrop=0230D -dollar=00024 -dopf=1D555 -dot=002D9 -doteq=02250 -doteqdot=02251 -dotminus=02238 -dotplus=02214 -dotsquare=022A1 -doublebarwedge=02306 -downarrow=02193 -downdownarrows=021CA -downharpoonleft=021C3 -downharpoonright=021C2 -drbkarow=02910 -drcorn=0231F -drcrop=0230C -dscr=1D4B9 -dscy=00455 -dsol=029F6 -dstrok=00111 -dtdot=022F1 -dtri=025BF -dtrif=025BE -duarr=021F5 -duhar=0296F -dwangle=029A6 -dzcy=0045F -dzigrarr=027FF -eDDot=02A77 -eDot=02251 -eacute=000E9 -easter=02A6E -ecaron=0011B -ecir=02256 -ecirc=000EA -ecolon=02255 -ecy=0044D -edot=00117 -ee=02147 -efDot=02252 -efr=1D522 -eg=02A9A -egrave=000E8 -egs=02A96 -egsdot=02A98 -el=02A99 -elinters=023E7 -ell=02113 -els=02A95 -elsdot=02A97 -emacr=00113 -empty=02205 -emptyset=02205 -emptyv=02205 -emsp13=02004 -emsp14=02005 -emsp=02003 -eng=0014B -ensp=02002 -eogon=00119 -eopf=1D556 -epar=022D5 -eparsl=029E3 -eplus=02A71 -epsi=003B5 -epsilon=003B5 -epsiv=003F5 -eqcirc=02256 -eqcolon=02255 -eqsim=02242 -eqslantgtr=02A96 -eqslantless=02A95 -equals=0003D -equest=0225F -equiv=02261 -equivDD=02A78 -eqvparsl=029E5 -erDot=02253 -erarr=02971 -escr=0212F -esdot=02250 -esim=02242 -eta=003B7 -eth=000F0 -euml=000EB -euro=020AC -excl=00021 -exist=02203 -expectation=02130 -exponentiale=02147 -fallingdotseq=02252 -fcy=00444 -female=02640 -ffilig=0FB03 -fflig=0FB00 -ffllig=0FB04 -ffr=1D523 -filig=0FB01 -flat=0266D -fllig=0FB02 -fltns=025B1 -fnof=00192 -fopf=1D557 -forall=02200 -fork=022D4 -forkv=02AD9 -fpartint=02A0D -frac12=000BD -frac13=02153 -frac14=000BC -frac15=02155 -frac16=02159 -frac18=0215B -frac23=02154 -frac25=02156 -frac34=000BE -frac35=02157 -frac38=0215C -frac45=02158 -frac56=0215A -frac58=0215D -frac78=0215E -frasl=02044 -frown=02322 -fscr=1D4BB -gE=02267 -gEl=02A8C -gacute=001F5 -gamma=003B3 -gammad=003DD -gap=02A86 -gbreve=0011F -gcirc=0011D -gcy=00433 -gdot=00121 -ge=02265 -gel=022DB -geq=02265 -geqq=02267 -geqslant=02A7E -ges=02A7E -gescc=02AA9 -gesdot=02A80 -gesdoto=02A82 -gesdotol=02A84 -gesles=02A94 -gfr=1D524 -gg=0226B -ggg=022D9 -gimel=02137 -gjcy=00453 -gl=02277 -glE=02A92 -gla=02AA5 -glj=02AA4 -gnE=02269 -gnap=02A8A -gnapprox=02A8A -gne=02A88 -gneq=02A88 -gneqq=02269 -gnsim=022E7 -gopf=1D558 -grave=00060 -gscr=0210A -gsim=02273 -gsime=02A8E -gsiml=02A90 -gt=0003E -gtcc=02AA7 -gtcir=02A7A -gtdot=022D7 -gtlPar=02995 -gtquest=02A7C -gtrapprox=02A86 -gtrarr=02978 -gtrdot=022D7 -gtreqless=022DB -gtreqqless=02A8C -gtrless=02277 -gtrsim=02273 -hArr=021D4 -hairsp=0200A -half=000BD -hamilt=0210B -hardcy=0044A -harr=02194 -harrcir=02948 -harrw=021AD -hbar=0210F -hcirc=00125 -hearts=02665 -heartsuit=02665 -hellip=02026 -hercon=022B9 -hfr=1D525 -hksearow=02925 -hkswarow=02926 -hoarr=021FF -homtht=0223B -hookleftarrow=021A9 -hookrightarrow=021AA -hopf=1D559 -horbar=02015 -hscr=1D4BD -hslash=0210F -hstrok=00127 -hybull=02043 -hyphen=02010 -iacute=000ED -ic=02063 -icirc=000EE -icy=00438 -iecy=00435 -iexcl=000A1 -iff=021D4 -ifr=1D526 -igrave=000EC -ii=02148 -iiiint=02A0C -iiint=0222D -iinfin=029DC -iiota=02129 -ijlig=00133 -imacr=0012B -image=02111 -imagline=02110 -imagpart=02111 -imath=00131 -imof=022B7 -imped=001B5 -in=02208 -incare=02105 -infin=0221E -infintie=029DD -inodot=00131 -int=0222B -intcal=022BA -integers=02124 -intercal=022BA -intlarhk=02A17 -intprod=02A3C -iocy=00451 -iogon=0012F -iopf=1D55A -iota=003B9 -iprod=02A3C -iquest=000BF -iscr=1D4BE -isin=02208 -isinE=022F9 -isindot=022F5 -isins=022F4 -isinsv=022F3 -isinv=02208 -it=02062 -itilde=00129 -iukcy=00456 -iuml=000EF -jcirc=00135 -jcy=00439 -jfr=1D527 -jmath=00237 -jopf=1D55B -jscr=1D4BF -jsercy=00458 -jukcy=00454 -kappa=003BA -kappav=003F0 -kcedil=00137 -kcy=0043A -kfr=1D528 -kgreen=00138 -khcy=00445 -kjcy=0045C -kopf=1D55C -kscr=1D4C0 -lAarr=021DA -lArr=021D0 -lAtail=0291B -lBarr=0290E -lE=02266 -lEg=02A8B -lHar=02962 -lacute=0013A -laemptyv=029B4 -lagran=02112 -lambda=003BB -lang=027E8 -langd=02991 -langle=027E8 -lap=02A85 -laquo=000AB -larr=02190 -larrb=021E4 -larrbfs=0291F -larrfs=0291D -larrhk=021A9 -larrlp=021AB -larrpl=02939 -larrsim=02973 -larrtl=021A2 -lat=02AAB -latail=02919 -late=02AAD -lbarr=0290C -lbbrk=02772 -lbrace=0007B -lbrack=0005B -lbrke=0298B -lbrksld=0298F -lbrkslu=0298D -lcaron=0013E -lcedil=0013C -lceil=02308 -lcub=0007B -lcy=0043B -ldca=02936 -ldquo=0201C -ldquor=0201E -ldrdhar=02967 -ldrushar=0294B -ldsh=021B2 -le=02264 -leftarrow=02190 -leftarrowtail=021A2 -leftharpoondown=021BD -leftharpoonup=021BC -leftleftarrows=021C7 -leftrightarrow=02194 -leftrightarrows=021C6 -leftrightharpoons=021CB -leftrightsquigarrow=021AD -leftthreetimes=022CB -leg=022DA -leq=02264 -leqq=02266 -leqslant=02A7D -les=02A7D -lescc=02AA8 -lesdot=02A7F -lesdoto=02A81 -lesdotor=02A83 -lesges=02A93 -lessapprox=02A85 -lessdot=022D6 -lesseqgtr=022DA -lesseqqgtr=02A8B -lessgtr=02276 -lesssim=02272 -lfisht=0297C -lfloor=0230A -lfr=1D529 -lg=02276 -lgE=02A91 -lhard=021BD -lharu=021BC -lharul=0296A -lhblk=02584 -ljcy=00459 -ll=0226A -llarr=021C7 -llcorner=0231E -llhard=0296B -lltri=025FA -lmidot=00140 -lmoust=023B0 -lmoustache=023B0 -lnE=02268 -lnap=02A89 -lnapprox=02A89 -lne=02A87 -lneq=02A87 -lneqq=02268 -lnsim=022E6 -loang=027EC -loarr=021FD -lobrk=027E6 -longleftarrow=027F5 -longleftrightarrow=027F7 -longmapsto=027FC -longrightarrow=027F6 -looparrowleft=021AB -looparrowright=021AC -lopar=02985 -lopf=1D55D -loplus=02A2D -lotimes=02A34 -lowast=02217 -lowbar=0005F -loz=025CA -lozenge=025CA -lozf=029EB -lpar=00028 -lparlt=02993 -lrarr=021C6 -lrcorner=0231F -lrhar=021CB -lrhard=0296D -lrm=0200E -lrtri=022BF -lsaquo=02039 -lscr=1D4C1 -lsh=021B0 -lsim=02272 -lsime=02A8D -lsimg=02A8F -lsqb=0005B -lsquo=02018 -lsquor=0201A -lstrok=00142 -lt=0003C -ltcc=02AA6 -ltcir=02A79 -ltdot=022D6 -lthree=022CB -ltimes=022C9 -ltlarr=02976 -ltquest=02A7B -ltrPar=02996 -ltri=025C3 -ltrie=022B4 -ltrif=025C2 -lurdshar=0294A -luruhar=02966 -mDDot=0223A -macr=000AF -male=02642 -malt=02720 -maltese=02720 -map=021A6 -mapsto=021A6 -mapstodown=021A7 -mapstoleft=021A4 -mapstoup=021A5 -marker=025AE -mcomma=02A29 -mcy=0043C -mdash=02014 -measuredangle=02221 -mfr=1D52A -mho=02127 -micro=000B5 -mid=02223 -midast=0002A -midcir=02AF0 -middot=000B7 -minus=02212 -minusb=0229F -minusd=02238 -minusdu=02A2A -mlcp=02ADB -mldr=02026 -mnplus=02213 -models=022A7 -mopf=1D55E -mp=02213 -mscr=1D4C2 -mstpos=0223E -mu=003BC -multimap=022B8 -mumap=022B8 -nLeftarrow=021CD -nLeftrightarrow=021CE -nRightarrow=021CF -nVDash=022AF -nVdash=022AE -nabla=02207 -nacute=00144 -nap=02249 -napos=00149 -napprox=02249 -natur=0266E -natural=0266E -naturals=02115 -nbsp=000A0 -ncap=02A43 -ncaron=00148 -ncedil=00146 -ncong=02247 -ncup=02A42 -ncy=0043D -ndash=02013 -ne=02260 -neArr=021D7 -nearhk=02924 -nearr=02197 -nearrow=02197 -nequiv=02262 -nesear=02928 -nexist=02204 -nexists=02204 -nfr=1D52B -nge=02271 -ngeq=02271 -ngsim=02275 -ngt=0226F -ngtr=0226F -nhArr=021CE -nharr=021AE -nhpar=02AF2 -ni=0220B -nis=022FC -nisd=022FA -niv=0220B -njcy=0045A -nlArr=021CD -nlarr=0219A -nldr=02025 -nle=02270 -nleftarrow=0219A -nleftrightarrow=021AE -nleq=02270 -nless=0226E -nlsim=02274 -nlt=0226E -nltri=022EA -nltrie=022EC -nmid=02224 -nopf=1D55F -not=000AC -notin=02209 -notinva=02209 -notinvb=022F7 -notinvc=022F6 -notni=0220C -notniva=0220C -notnivb=022FE -notnivc=022FD -npar=02226 -nparallel=02226 -npolint=02A14 -npr=02280 -nprcue=022E0 -nprec=02280 -nrArr=021CF -nrarr=0219B -nrightarrow=0219B -nrtri=022EB -nrtrie=022ED -nsc=02281 -nsccue=022E1 -nscr=1D4C3 -nshortmid=02224 -nshortparallel=02226 -nsim=02241 -nsime=02244 -nsimeq=02244 -nsmid=02224 -nspar=02226 -nsqsube=022E2 -nsqsupe=022E3 -nsub=02284 -nsube=02288 -nsubseteq=02288 -nsucc=02281 -nsup=02285 -nsupe=02289 -nsupseteq=02289 -ntgl=02279 -ntilde=000F1 -ntlg=02278 -ntriangleleft=022EA -ntrianglelefteq=022EC -ntriangleright=022EB -ntrianglerighteq=022ED -nu=003BD -num=00023 -numero=02116 -numsp=02007 -nvDash=022AD -nvHarr=02904 -nvdash=022AC -nvinfin=029DE -nvlArr=02902 -nvrArr=02903 -nwArr=021D6 -nwarhk=02923 -nwarr=02196 -nwarrow=02196 -nwnear=02927 -oS=024C8 -oacute=000F3 -oast=0229B -ocir=0229A -ocirc=000F4 -ocy=0043E -odash=0229D -odblac=00151 -odiv=02A38 -odot=02299 -odsold=029BC -oelig=00153 -ofcir=029BF -ofr=1D52C -ogon=002DB -ograve=000F2 -ogt=029C1 -ohbar=029B5 -ohm=003A9 -oint=0222E -olarr=021BA -olcir=029BE -olcross=029BB -oline=0203E -olt=029C0 -omacr=0014D -omega=003C9 -omicron=003BF -omid=029B6 -ominus=02296 -oopf=1D560 -opar=029B7 -operp=029B9 -oplus=02295 -or=02228 -orarr=021BB -ord=02A5D -order=02134 -orderof=02134 -ordf=000AA -ordm=000BA -origof=022B6 -oror=02A56 -orslope=02A57 -orv=02A5B -oscr=02134 -oslash=000F8 -osol=02298 -otilde=000F5 -otimes=02297 -otimesas=02A36 -ouml=000F6 -ovbar=0233D -par=02225 -para=000B6 -parallel=02225 -parsim=02AF3 -parsl=02AFD -part=02202 -pcy=0043F -percnt=00025 -period=0002E -permil=02030 -perp=022A5 -pertenk=02031 -pfr=1D52D -phi=003C6 -phiv=003D5 -phmmat=02133 -phone=0260E -pi=003C0 -pitchfork=022D4 -piv=003D6 -planck=0210F -planckh=0210E -plankv=0210F -plus=0002B -plusacir=02A23 -plusb=0229E -pluscir=02A22 -plusdo=02214 -plusdu=02A25 -pluse=02A72 -plusmn=000B1 -plussim=02A26 -plustwo=02A27 -pm=000B1 -pointint=02A15 -popf=1D561 -pound=000A3 -pr=0227A -prE=02AB3 -prap=02AB7 -prcue=0227C -pre=02AAF -prec=0227A -precapprox=02AB7 -preccurlyeq=0227C -preceq=02AAF -precnapprox=02AB9 -precneqq=02AB5 -precnsim=022E8 -precsim=0227E -prime=02032 -primes=02119 -prnE=02AB5 -prnap=02AB9 -prnsim=022E8 -prod=0220F -profalar=0232E -profline=02312 -profsurf=02313 -prop=0221D -propto=0221D -prsim=0227E -prurel=022B0 -pscr=1D4C5 -psi=003C8 -puncsp=02008 -qfr=1D52E -qint=02A0C -qopf=1D562 -qprime=02057 -qscr=1D4C6 -quaternions=0210D -quatint=02A16 -quest=0003F -questeq=0225F -quot=00022 -rAarr=021DB -rArr=021D2 -rAtail=0291C -rBarr=0290F -rHar=02964 -racute=00155 -radic=0221A -raemptyv=029B3 -rang=027E9 -rangd=02992 -range=029A5 -rangle=027E9 -raquo=000BB -rarr=02192 -rarrap=02975 -rarrb=021E5 -rarrbfs=02920 -rarrc=02933 -rarrfs=0291E -rarrhk=021AA -rarrlp=021AC -rarrpl=02945 -rarrsim=02974 -rarrtl=021A3 -rarrw=0219D -ratail=0291A -ratio=02236 -rationals=0211A -rbarr=0290D -rbbrk=02773 -rbrace=0007D -rbrack=0005D -rbrke=0298C -rbrksld=0298E -rbrkslu=02990 -rcaron=00159 -rcedil=00157 -rceil=02309 -rcub=0007D -rcy=00440 -rdca=02937 -rdldhar=02969 -rdquo=0201D -rdquor=0201D -rdsh=021B3 -real=0211C -realine=0211B -realpart=0211C -reals=0211D -rect=025AD -reg=000AE -rfisht=0297D -rfloor=0230B -rfr=1D52F -rhard=021C1 -rharu=021C0 -rharul=0296C -rho=003C1 -rhov=003F1 -rightarrow=02192 -rightarrowtail=021A3 -rightharpoondown=021C1 -rightharpoonup=021C0 -rightleftarrows=021C4 -rightleftharpoons=021CC -rightrightarrows=021C9 -rightsquigarrow=0219D -rightthreetimes=022CC -ring=002DA -risingdotseq=02253 -rlarr=021C4 -rlhar=021CC -rlm=0200F -rmoust=023B1 -rmoustache=023B1 -rnmid=02AEE -roang=027ED -roarr=021FE -robrk=027E7 -ropar=02986 -ropf=1D563 -roplus=02A2E -rotimes=02A35 -rpar=00029 -rpargt=02994 -rppolint=02A12 -rrarr=021C9 -rsaquo=0203A -rscr=1D4C7 -rsh=021B1 -rsqb=0005D -rsquo=02019 -rsquor=02019 -rthree=022CC -rtimes=022CA -rtri=025B9 -rtrie=022B5 -rtrif=025B8 -rtriltri=029CE -ruluhar=02968 -rx=0211E -sacute=0015B -sbquo=0201A -sc=0227B -scE=02AB4 -scap=02AB8 -scaron=00161 -sccue=0227D -sce=02AB0 -scedil=0015F -scirc=0015D -scnE=02AB6 -scnap=02ABA -scnsim=022E9 -scpolint=02A13 -scsim=0227F -scy=00441 -sdot=022C5 -sdotb=022A1 -sdote=02A66 -seArr=021D8 -searhk=02925 -searr=02198 -searrow=02198 -sect=000A7 -semi=0003B -seswar=02929 -setminus=02216 -setmn=02216 -sext=02736 -sfr=1D530 -sfrown=02322 -sharp=0266F -shchcy=00449 -shcy=00448 -shortmid=02223 -shortparallel=02225 -shy=000AD -sigma=003C3 -sigmaf=003C2 -sigmav=003C2 -sim=0223C -simdot=02A6A -sime=02243 -simeq=02243 -simg=02A9E -simgE=02AA0 -siml=02A9D -simlE=02A9F -simne=02246 -simplus=02A24 -simrarr=02972 -slarr=02190 -smallsetminus=02216 -smashp=02A33 -smeparsl=029E4 -smid=02223 -smile=02323 -smt=02AAA -smte=02AAC -softcy=0044C -sol=0002F -solb=029C4 -solbar=0233F -sopf=1D564 -spades=02660 -spadesuit=02660 -spar=02225 -sqcap=02293 -sqcup=02294 -sqsub=0228F -sqsube=02291 -sqsubset=0228F -sqsubseteq=02291 -sqsup=02290 -sqsupe=02292 -sqsupset=02290 -sqsupseteq=02292 -squ=025A1 -square=025A1 -squarf=025AA -squf=025AA -srarr=02192 -sscr=1D4C8 -ssetmn=02216 -ssmile=02323 -sstarf=022C6 -star=02606 -starf=02605 -straightepsilon=003F5 -straightphi=003D5 -strns=000AF -sub=02282 -subE=02AC5 -subdot=02ABD -sube=02286 -subedot=02AC3 -submult=02AC1 -subnE=02ACB -subne=0228A -subplus=02ABF -subrarr=02979 -subset=02282 -subseteq=02286 -subseteqq=02AC5 -subsetneq=0228A -subsetneqq=02ACB -subsim=02AC7 -subsub=02AD5 -subsup=02AD3 -succ=0227B -succapprox=02AB8 -succcurlyeq=0227D -succeq=02AB0 -succnapprox=02ABA -succneqq=02AB6 -succnsim=022E9 -succsim=0227F -sum=02211 -sung=0266A -sup1=000B9 -sup2=000B2 -sup3=000B3 -sup=02283 -supE=02AC6 -supdot=02ABE -supdsub=02AD8 -supe=02287 -supedot=02AC4 -suphsol=027C9 -suphsub=02AD7 -suplarr=0297B -supmult=02AC2 -supnE=02ACC -supne=0228B -supplus=02AC0 -supset=02283 -supseteq=02287 -supseteqq=02AC6 -supsetneq=0228B -supsetneqq=02ACC -supsim=02AC8 -supsub=02AD4 -supsup=02AD6 -swArr=021D9 -swarhk=02926 -swarr=02199 -swarrow=02199 -swnwar=0292A -szlig=000DF -target=02316 -tau=003C4 -tbrk=023B4 -tcaron=00165 -tcedil=00163 -tcy=00442 -tdot=020DB -telrec=02315 -tfr=1D531 -there4=02234 -therefore=02234 -theta=003B8 -thetasym=003D1 -thetav=003D1 -thickapprox=02248 -thicksim=0223C -thinsp=02009 -thkap=02248 -thksim=0223C -thorn=000FE -tilde=002DC -times=000D7 -timesb=022A0 -timesbar=02A31 -timesd=02A30 -tint=0222D -toea=02928 -top=022A4 -topbot=02336 -topcir=02AF1 -topf=1D565 -topfork=02ADA -tosa=02929 -tprime=02034 -trade=02122 -triangle=025B5 -triangledown=025BF -triangleleft=025C3 -trianglelefteq=022B4 -triangleq=0225C -triangleright=025B9 -trianglerighteq=022B5 -tridot=025EC -trie=0225C -triminus=02A3A -triplus=02A39 -trisb=029CD -tritime=02A3B -trpezium=023E2 -tscr=1D4C9 -tscy=00446 -tshcy=0045B -tstrok=00167 -twixt=0226C -twoheadleftarrow=0219E -twoheadrightarrow=021A0 -uArr=021D1 -uHar=02963 -uacute=000FA -uarr=02191 -ubrcy=0045E -ubreve=0016D -ucirc=000FB -ucy=00443 -udarr=021C5 -udblac=00171 -udhar=0296E -ufisht=0297E -ufr=1D532 -ugrave=000F9 -uharl=021BF -uharr=021BE -uhblk=02580 -ulcorn=0231C -ulcorner=0231C -ulcrop=0230F -ultri=025F8 -umacr=0016B -uml=000A8 -uogon=00173 -uopf=1D566 -uparrow=02191 -updownarrow=02195 -upharpoonleft=021BF -upharpoonright=021BE -uplus=0228E -upsi=003C5 -upsih=003D2 -upsilon=003C5 -upuparrows=021C8 -urcorn=0231D -urcorner=0231D -urcrop=0230E -uring=0016F -urtri=025F9 -uscr=1D4CA -utdot=022F0 -utilde=00169 -utri=025B5 -utrif=025B4 -uuarr=021C8 -uuml=000FC -uwangle=029A7 -vArr=021D5 -vBar=02AE8 -vBarv=02AE9 -vDash=022A8 -vangrt=0299C -varepsilon=003F5 -varkappa=003F0 -varnothing=02205 -varphi=003D5 -varpi=003D6 -varpropto=0221D -varr=02195 -varrho=003F1 -varsigma=003C2 -vartheta=003D1 -vartriangleleft=022B2 -vartriangleright=022B3 -vcy=00432 -vdash=022A2 -vee=02228 -veebar=022BB -veeeq=0225A -vellip=022EE -verbar=0007C -vert=0007C -vfr=1D533 -vltri=022B2 -vopf=1D567 -vprop=0221D -vrtri=022B3 -vscr=1D4CB -vzigzag=0299A -wcirc=00175 -wedbar=02A5F -wedge=02227 -wedgeq=02259 -weierp=02118 -wfr=1D534 -wopf=1D568 -wp=02118 -wr=02240 -wreath=02240 -wscr=1D4CC -xcap=022C2 -xcirc=025EF -xcup=022C3 -xdtri=025BD -xfr=1D535 -xhArr=027FA -xharr=027F7 -xi=003BE -xlArr=027F8 -xlarr=027F5 -xmap=027FC -xnis=022FB -xodot=02A00 -xopf=1D569 -xoplus=02A01 -xotime=02A02 -xrArr=027F9 -xrarr=027F6 -xscr=1D4CD -xsqcup=02A06 -xuplus=02A04 -xutri=025B3 -xvee=022C1 -xwedge=022C0 -yacute=000FD -yacy=0044F -ycirc=00177 -ycy=0044B -yen=000A5 -yfr=1D536 -yicy=00457 -yopf=1D56A -yscr=1D4CE -yucy=0044E -yuml=000FF -zacute=0017A -zcaron=0017E -zcy=00437 -zdot=0017C -zeetrf=02128 -zeta=003B6 -zfr=1D537 -zhcy=00436 -zigrarr=021DD -zopf=1D56B -zscr=1D4CF -zwj=0200D -zwnj=0200C +AElig=5i;2v +AMP=12;8 +Aacute=5d;2p +Abreve=76;4k +Acirc=5e;2q +Acy=sw;av +Afr=2kn8;1kh +Agrave=5c;2o +Alpha=pd;8d +Amacr=74;4i +And=8cz;1e1 +Aogon=78;4m +Aopf=2koo;1ls +ApplyFunction=6e9;ew +Aring=5h;2t +Ascr=2kkc;1jc +Assign=6s4;s6 +Atilde=5f;2r +Auml=5g;2s +Backslash=6qe;o1 +Barv=8h3;1it +Barwed=6x2;120 +Bcy=sx;aw +Because=6r9;pw +Bernoullis=6jw;gn +Beta=pe;8e +Bfr=2kn9;1ki +Bopf=2kop;1lt +Breve=k8;82 +Bscr=6jw;gp +Bumpeq=6ry;ro +CHcy=tj;bi +COPY=4p;1q +Cacute=7a;4o +Cap=6vm;zz +CapitalDifferentialD=6kl;h8 +Cayleys=6jx;gq +Ccaron=7g;4u +Ccedil=5j;2w +Ccirc=7c;4q +Cconint=6r4;pn +Cdot=7e;4s +Cedilla=54;2e +CenterDot=53;2b +Cfr=6jx;gr +Chi=pz;8y +CircleDot=6u1;x8 +CircleMinus=6ty;x3 +CirclePlus=6tx;x1 +CircleTimes=6tz;x5 +ClockwiseContourIntegral=6r6;pp +CloseCurlyDoubleQuote=6cd;e0 +CloseCurlyQuote=6c9;dt +Colon=6rb;q1 +Colone=8dw;1en +Congruent=6sh;sn +Conint=6r3;pm +ContourIntegral=6r2;pi +Copf=6iq;f7 +Coproduct=6q8;nq +CounterClockwiseContourIntegral=6r7;pr +Cross=8bz;1d8 +Cscr=2kke;1jd +Cup=6vn;100 +CupCap=6rx;rk +DD=6kl;h9 +DDotrahd=841;184 +DJcy=si;ai +DScy=sl;al +DZcy=sv;au +Dagger=6ch;e7 +Darr=6n5;j5 +Dashv=8h0;1ir +Dcaron=7i;4w +Dcy=t0;az +Del=6pz;n9 +Delta=pg;8g +Dfr=2knb;1kj +DiacriticalAcute=50;27 +DiacriticalDot=k9;84 +DiacriticalDoubleAcute=kd;8a +DiacriticalGrave=2o;13 +DiacriticalTilde=kc;88 +Diamond=6v8;za +DifferentialD=6km;ha +Dopf=2kor;1lu +Dot=4o;1n +DotDot=6ho;f5 +DotEqual=6s0;rw +DoubleContourIntegral=6r3;pl +DoubleDot=4o;1m +DoubleDownArrow=6oj;m0 +DoubleLeftArrow=6og;lq +DoubleLeftRightArrow=6ok;m3 +DoubleLeftTee=8h0;1iq +DoubleLongLeftArrow=7w8;17g +DoubleLongLeftRightArrow=7wa;17m +DoubleLongRightArrow=7w9;17j +DoubleRightArrow=6oi;lw +DoubleRightTee=6ug;xz +DoubleUpArrow=6oh;lt +DoubleUpDownArrow=6ol;m7 +DoubleVerticalBar=6qt;ov +DownArrow=6mr;i8 +DownArrowBar=843;186 +DownArrowUpArrow=6ph;mn +DownBreve=lt;8c +DownLeftRightVector=85s;198 +DownLeftTeeVector=866;19m +DownLeftVector=6nx;ke +DownLeftVectorBar=85y;19e +DownRightTeeVector=867;19n +DownRightVector=6o1;kq +DownRightVectorBar=85z;19f +DownTee=6uc;xs +DownTeeArrow=6nb;jh +Downarrow=6oj;m1 +Dscr=2kkf;1je +Dstrok=7k;4y +ENG=96;6g +ETH=5s;35 +Eacute=5l;2y +Ecaron=7u;56 +Ecirc=5m;2z +Ecy=tp;bo +Edot=7q;52 +Efr=2knc;1kk +Egrave=5k;2x +Element=6q0;na +Emacr=7m;50 +EmptySmallSquare=7i3;15x +EmptyVerySmallSquare=7fv;150 +Eogon=7s;54 +Eopf=2kos;1lv +Epsilon=ph;8h +Equal=8dx;1eo +EqualTilde=6rm;qp +Equilibrium=6oc;li +Escr=6k0;gu +Esim=8dv;1em +Eta=pj;8j +Euml=5n;30 +Exists=6pv;mz +ExponentialE=6kn;hc +Fcy=tg;bf +Ffr=2knd;1kl +FilledSmallSquare=7i4;15y +FilledVerySmallSquare=7fu;14w +Fopf=2kot;1lw +ForAll=6ps;ms +Fouriertrf=6k1;gv +Fscr=6k1;gw +GJcy=sj;aj +GT=1q;r +Gamma=pf;8f +Gammad=rg;a5 +Gbreve=7y;5a +Gcedil=82;5e +Gcirc=7w;58 +Gcy=sz;ay +Gdot=80;5c +Gfr=2kne;1km +Gg=6vt;10c +Gopf=2kou;1lx +GreaterEqual=6sl;sv +GreaterEqualLess=6vv;10i +GreaterFullEqual=6sn;t6 +GreaterGreater=8f6;1gh +GreaterLess=6t3;ul +GreaterSlantEqual=8e6;1f5 +GreaterTilde=6sz;ub +Gscr=2kki;1jf +Gt=6sr;tr +HARDcy=tm;bl +Hacek=jr;80 +Hat=2m;10 +Hcirc=84;5f +Hfr=6j0;fe +HilbertSpace=6iz;fa +Hopf=6j1;fg +HorizontalLine=7b4;13i +Hscr=6iz;fc +Hstrok=86;5h +HumpDownHump=6ry;rn +HumpEqual=6rz;rs +IEcy=t1;b0 +IJlig=8i;5s +IOcy=sh;ah +Iacute=5p;32 +Icirc=5q;33 +Icy=t4;b3 +Idot=8g;5p +Ifr=6j5;fq +Igrave=5o;31 +Im=6j5;fr +Imacr=8a;5l +ImaginaryI=6ko;hf +Implies=6oi;ly +Int=6r0;pf +Integral=6qz;pd +Intersection=6v6;z4 +InvisibleComma=6eb;f0 +InvisibleTimes=6ea;ey +Iogon=8e;5n +Iopf=2kow;1ly +Iota=pl;8l +Iscr=6j4;fn +Itilde=88;5j +Iukcy=sm;am +Iuml=5r;34 +Jcirc=8k;5u +Jcy=t5;b4 +Jfr=2knh;1kn +Jopf=2kox;1lz +Jscr=2kkl;1jg +Jsercy=so;ao +Jukcy=sk;ak +KHcy=th;bg +KJcy=ss;as +Kappa=pm;8m +Kcedil=8m;5w +Kcy=t6;b5 +Kfr=2kni;1ko +Kopf=2koy;1m0 +Kscr=2kkm;1jh +LJcy=sp;ap +LT=1o;m +Lacute=8p;5z +Lambda=pn;8n +Lang=7vu;173 +Laplacetrf=6j6;fs +Larr=6n2;j1 +Lcaron=8t;63 +Lcedil=8r;61 +Lcy=t7;b6 +LeftAngleBracket=7vs;16x +LeftArrow=6mo;hu +LeftArrowBar=6p0;mj +LeftArrowRightArrow=6o6;l3 +LeftCeiling=6x4;121 +LeftDoubleBracket=7vq;16t +LeftDownTeeVector=869;19p +LeftDownVector=6o3;kw +LeftDownVectorBar=861;19h +LeftFloor=6x6;125 +LeftRightArrow=6ms;ib +LeftRightVector=85q;196 +LeftTee=6ub;xq +LeftTeeArrow=6n8;ja +LeftTeeVector=862;19i +LeftTriangle=6uq;ya +LeftTriangleBar=89b;1c0 +LeftTriangleEqual=6us;yg +LeftUpDownVector=85t;199 +LeftUpTeeVector=868;19o +LeftUpVector=6nz;kk +LeftUpVectorBar=860;19g +LeftVector=6nw;kb +LeftVectorBar=85u;19a +Leftarrow=6og;lr +Leftrightarrow=6ok;m4 +LessEqualGreater=6vu;10e +LessFullEqual=6sm;t0 +LessGreater=6t2;ui +LessLess=8f5;1gf +LessSlantEqual=8e5;1ez +LessTilde=6sy;u8 +Lfr=2knj;1kp +Ll=6vs;109 +Lleftarrow=6oq;me +Lmidot=8v;65 +LongLeftArrow=7w5;177 +LongLeftRightArrow=7w7;17d +LongRightArrow=7w6;17a +Longleftarrow=7w8;17h +Longleftrightarrow=7wa;17n +Longrightarrow=7w9;17k +Lopf=2koz;1m1 +LowerLeftArrow=6mx;iq +LowerRightArrow=6mw;in +Lscr=6j6;fu +Lsh=6nk;jv +Lstrok=8x;67 +Lt=6sq;tl +Map=83p;17v +Mcy=t8;b7 +MediumSpace=6e7;eu +Mellintrf=6k3;gx +Mfr=2knk;1kq +MinusPlus=6qb;nv +Mopf=2kp0;1m2 +Mscr=6k3;gz +Mu=po;8o +NJcy=sq;aq +Nacute=8z;69 +Ncaron=93;6d +Ncedil=91;6b +Ncy=t9;b8 +NegativeMediumSpace=6bv;dc +NegativeThickSpace=6bv;dd +NegativeThinSpace=6bv;de +NegativeVeryThinSpace=6bv;db +NestedGreaterGreater=6sr;tq +NestedLessLess=6sq;tk +NewLine=a;1 +Nfr=2knl;1kr +NoBreak=6e8;ev +NonBreakingSpace=4g;1d +Nopf=6j9;fx +Not=8h8;1ix +NotCongruent=6si;sp +NotCupCap=6st;tv +NotDoubleVerticalBar=6qu;p0 +NotElement=6q1;ne +NotEqual=6sg;sk +NotEqualTilde=6rm,mw;qn +NotExists=6pw;n1 +NotGreater=6sv;tz +NotGreaterEqual=6sx;u5 +NotGreaterFullEqual=6sn,mw;t3 +NotGreaterGreater=6sr,mw;tn +NotGreaterLess=6t5;uq +NotGreaterSlantEqual=8e6,mw;1f2 +NotGreaterTilde=6t1;ug +NotHumpDownHump=6ry,mw;rl +NotHumpEqual=6rz,mw;rq +NotLeftTriangle=6wa;113 +NotLeftTriangleBar=89b,mw;1bz +NotLeftTriangleEqual=6wc;119 +NotLess=6su;tw +NotLessEqual=6sw;u2 +NotLessGreater=6t4;uo +NotLessLess=6sq,mw;th +NotLessSlantEqual=8e5,mw;1ew +NotLessTilde=6t0;ue +NotNestedGreaterGreater=8f6,mw;1gg +NotNestedLessLess=8f5,mw;1ge +NotPrecedes=6tc;vb +NotPrecedesEqual=8fj,mw;1gv +NotPrecedesSlantEqual=6w0;10p +NotReverseElement=6q4;nl +NotRightTriangle=6wb;116 +NotRightTriangleBar=89c,mw;1c1 +NotRightTriangleEqual=6wd;11c +NotSquareSubset=6tr,mw;wh +NotSquareSubsetEqual=6w2;10t +NotSquareSuperset=6ts,mw;wl +NotSquareSupersetEqual=6w3;10v +NotSubset=6te,6he;vh +NotSubsetEqual=6tk;w0 +NotSucceeds=6td;ve +NotSucceedsEqual=8fk,mw;1h1 +NotSucceedsSlantEqual=6w1;10r +NotSucceedsTilde=6tb,mw;v7 +NotSuperset=6tf,6he;vm +NotSupersetEqual=6tl;w3 +NotTilde=6rl;ql +NotTildeEqual=6ro;qv +NotTildeFullEqual=6rr;r1 +NotTildeTilde=6rt;r9 +NotVerticalBar=6qs;or +Nscr=2kkp;1ji +Ntilde=5t;36 +Nu=pp;8p +OElig=9e;6m +Oacute=5v;38 +Ocirc=5w;39 +Ocy=ta;b9 +Odblac=9c;6k +Ofr=2knm;1ks +Ograve=5u;37 +Omacr=98;6i +Omega=q1;90 +Omicron=pr;8r +Oopf=2kp2;1m3 +OpenCurlyDoubleQuote=6cc;dy +OpenCurlyQuote=6c8;dr +Or=8d0;1e2 +Oscr=2kkq;1jj +Oslash=60;3d +Otilde=5x;3a +Otimes=8c7;1df +Ouml=5y;3b +OverBar=6da;em +OverBrace=732;13b +OverBracket=71w;134 +OverParenthesis=730;139 +PartialD=6pu;mx +Pcy=tb;ba +Pfr=2knn;1kt +Phi=py;8x +Pi=ps;8s +PlusMinus=4x;22 +Poincareplane=6j0;fd +Popf=6jd;g3 +Pr=8fv;1hl +Precedes=6t6;us +PrecedesEqual=8fj;1gy +PrecedesSlantEqual=6t8;uy +PrecedesTilde=6ta;v4 +Prime=6cz;eg +Product=6q7;no +Proportion=6rb;q0 +Proportional=6ql;oa +Pscr=2kkr;1jk +Psi=q0;8z +QUOT=y;3 +Qfr=2kno;1ku +Qopf=6je;g5 +Qscr=2kks;1jl +RBarr=840;183 +REG=4u;1x +Racute=9g;6o +Rang=7vv;174 +Rarr=6n4;j4 +Rarrtl=846;187 +Rcaron=9k;6s +Rcedil=9i;6q +Rcy=tc;bb +Re=6jg;gb +ReverseElement=6q3;nh +ReverseEquilibrium=6ob;le +ReverseUpEquilibrium=86n;1a4 +Rfr=6jg;ga +Rho=pt;8t +RightAngleBracket=7vt;170 +RightArrow=6mq;i3 +RightArrowBar=6p1;ml +RightArrowLeftArrow=6o4;ky +RightCeiling=6x5;123 +RightDoubleBracket=7vr;16v +RightDownTeeVector=865;19l +RightDownVector=6o2;kt +RightDownVectorBar=85x;19d +RightFloor=6x7;127 +RightTee=6ua;xo +RightTeeArrow=6na;je +RightTeeVector=863;19j +RightTriangle=6ur;yd +RightTriangleBar=89c;1c2 +RightTriangleEqual=6ut;yk +RightUpDownVector=85r;197 +RightUpTeeVector=864;19k +RightUpVector=6ny;kh +RightUpVectorBar=85w;19c +RightVector=6o0;kn +RightVectorBar=85v;19b +Rightarrow=6oi;lx +Ropf=6jh;gd +RoundImplies=86o;1a6 +Rrightarrow=6or;mg +Rscr=6jf;g7 +Rsh=6nl;jx +RuleDelayed=8ac;1cb +SHCHcy=tl;bk +SHcy=tk;bj +SOFTcy=to;bn +Sacute=9m;6u +Sc=8fw;1hm +Scaron=9s;70 +Scedil=9q;6y +Scirc=9o;6w +Scy=td;bc +Sfr=2knq;1kv +ShortDownArrow=6mr;i7 +ShortLeftArrow=6mo;ht +ShortRightArrow=6mq;i2 +ShortUpArrow=6mp;hy +Sigma=pv;8u +SmallCircle=6qg;o6 +Sopf=2kp6;1m4 +Sqrt=6qi;o9 +Square=7fl;14t +SquareIntersection=6tv;ww +SquareSubset=6tr;wi +SquareSubsetEqual=6tt;wp +SquareSuperset=6ts;wm +SquareSupersetEqual=6tu;ws +SquareUnion=6tw;wz +Sscr=2kku;1jm +Star=6va;zf +Sub=6vk;zw +Subset=6vk;zv +SubsetEqual=6ti;vu +Succeeds=6t7;uv +SucceedsEqual=8fk;1h4 +SucceedsSlantEqual=6t9;v1 +SucceedsTilde=6tb;v8 +SuchThat=6q3;ni +Sum=6q9;ns +Sup=6vl;zy +Superset=6tf;vp +SupersetEqual=6tj;vx +Supset=6vl;zx +THORN=66;3j +TRADE=6jm;gf +TSHcy=sr;ar +TScy=ti;bh +Tab=9;0 +Tau=pw;8v +Tcaron=9w;74 +Tcedil=9u;72 +Tcy=te;bd +Tfr=2knr;1kw +Therefore=6r8;pt +Theta=pk;8k +ThickSpace=6e7,6bu;et +ThinSpace=6bt;d7 +Tilde=6rg;q9 +TildeEqual=6rn;qs +TildeFullEqual=6rp;qy +TildeTilde=6rs;r4 +Topf=2kp7;1m5 +TripleDot=6hn;f3 +Tscr=2kkv;1jn +Tstrok=9y;76 +Uacute=62;3f +Uarr=6n3;j2 +Uarrocir=85l;193 +Ubrcy=su;at +Ubreve=a4;7c +Ucirc=63;3g +Ucy=tf;be +Udblac=a8;7g +Ufr=2kns;1kx +Ugrave=61;3e +Umacr=a2;7a +UnderBar=2n;11 +UnderBrace=733;13c +UnderBracket=71x;136 +UnderParenthesis=731;13a +Union=6v7;z8 +UnionPlus=6tq;wf +Uogon=aa;7i +Uopf=2kp8;1m6 +UpArrow=6mp;hz +UpArrowBar=842;185 +UpArrowDownArrow=6o5;l1 +UpDownArrow=6mt;ie +UpEquilibrium=86m;1a2 +UpTee=6ud;xv +UpTeeArrow=6n9;jc +Uparrow=6oh;lu +Updownarrow=6ol;m8 +UpperLeftArrow=6mu;ih +UpperRightArrow=6mv;ik +Upsi=r6;9z +Upsilon=px;8w +Uring=a6;7e +Uscr=2kkw;1jo +Utilde=a0;78 +Uuml=64;3h +VDash=6uj;y3 +Vbar=8h7;1iw +Vcy=sy;ax +Vdash=6uh;y1 +Vdashl=8h2;1is +Vee=6v5;z3 +Verbar=6c6;dp +Vert=6c6;dq +VerticalBar=6qr;on +VerticalLine=3g;18 +VerticalSeparator=7rs;16o +VerticalTilde=6rk;qi +VeryThinSpace=6bu;d9 +Vfr=2knt;1ky +Vopf=2kp9;1m7 +Vscr=2kkx;1jp +Vvdash=6ui;y2 +Wcirc=ac;7k +Wedge=6v4;z0 +Wfr=2knu;1kz +Wopf=2kpa;1m8 +Wscr=2kky;1jq +Xfr=2knv;1l0 +Xi=pq;8q +Xopf=2kpb;1m9 +Xscr=2kkz;1jr +YAcy=tr;bq +YIcy=sn;an +YUcy=tq;bp +Yacute=65;3i +Ycirc=ae;7m +Ycy=tn;bm +Yfr=2knw;1l1 +Yopf=2kpc;1ma +Yscr=2kl0;1js +Yuml=ag;7o +ZHcy=t2;b1 +Zacute=ah;7p +Zcaron=al;7t +Zcy=t3;b2 +Zdot=aj;7r +ZeroWidthSpace=6bv;df +Zeta=pi;8i +Zfr=6js;gl +Zopf=6jo;gi +Zscr=2kl1;1jt +aacute=69;3m +abreve=77;4l +ac=6ri;qg +acE=6ri,mr;qe +acd=6rj;qh +acirc=6a;3n +acute=50;28 +acy=ts;br +aelig=6e;3r +af=6e9;ex +afr=2kny;1l2 +agrave=68;3l +alefsym=6k5;h3 +aleph=6k5;h4 +alpha=q9;92 +amacr=75;4j +amalg=8cf;1dm +amp=12;9 +and=6qv;p6 +andand=8d1;1e3 +andd=8d8;1e9 +andslope=8d4;1e6 +andv=8d6;1e7 +ang=6qo;oj +ange=884;1b1 +angle=6qo;oi +angmsd=6qp;ol +angmsdaa=888;1b5 +angmsdab=889;1b6 +angmsdac=88a;1b7 +angmsdad=88b;1b8 +angmsdae=88c;1b9 +angmsdaf=88d;1ba +angmsdag=88e;1bb +angmsdah=88f;1bc +angrt=6qn;og +angrtvb=6v2;yw +angrtvbd=87x;1b0 +angsph=6qq;om +angst=5h;2u +angzarr=70c;12z +aogon=79;4n +aopf=2kpe;1mb +ap=6rs;r8 +apE=8ds;1ej +apacir=8dr;1eh +ape=6ru;rd +apid=6rv;rf +apos=13;a +approx=6rs;r5 +approxeq=6ru;rc +aring=6d;3q +ascr=2kl2;1ju +ast=16;e +asymp=6rs;r6 +asympeq=6rx;rj +atilde=6b;3o +auml=6c;3p +awconint=6r7;ps +awint=8b5;1cr +bNot=8h9;1iy +backcong=6rw;rg +backepsilon=s6;af +backprime=6d1;ei +backsim=6rh;qc +backsimeq=6vh;zp +barvee=6v1;yv +barwed=6x1;11y +barwedge=6x1;11x +bbrk=71x;137 +bbrktbrk=71y;138 +bcong=6rw;rh +bcy=tt;bs +bdquo=6ce;e4 +becaus=6r9;py +because=6r9;px +bemptyv=88g;1bd +bepsi=s6;ag +bernou=6jw;go +beta=qa;93 +beth=6k6;h5 +between=6ss;tt +bfr=2knz;1l3 +bigcap=6v6;z5 +bigcirc=7hr;15s +bigcup=6v7;z7 +bigodot=8ao;1cd +bigoplus=8ap;1cf +bigotimes=8aq;1ch +bigsqcup=8au;1cl +bigstar=7id;15z +bigtriangledown=7gd;15e +bigtriangleup=7g3;154 +biguplus=8as;1cj +bigvee=6v5;z1 +bigwedge=6v4;yy +bkarow=83x;17x +blacklozenge=8a3;1c9 +blacksquare=7fu;14x +blacktriangle=7g4;156 +blacktriangledown=7ge;15g +blacktriangleleft=7gi;15k +blacktriangleright=7g8;15a +blank=74z;13f +blk12=7f6;14r +blk14=7f5;14q +blk34=7f7;14s +block=7ew;14p +bne=1p,6hx;o +bnequiv=6sh,6hx;sm +bnot=6xc;12d +bopf=2kpf;1mc +bot=6ud;xx +bottom=6ud;xu +bowtie=6vc;zi +boxDL=7dj;141 +boxDR=7dg;13y +boxDl=7di;140 +boxDr=7df;13x +boxH=7dc;13u +boxHD=7dy;14g +boxHU=7e1;14j +boxHd=7dw;14e +boxHu=7dz;14h +boxUL=7dp;147 +boxUR=7dm;144 +boxUl=7do;146 +boxUr=7dl;143 +boxV=7dd;13v +boxVH=7e4;14m +boxVL=7dv;14d +boxVR=7ds;14a +boxVh=7e3;14l +boxVl=7du;14c +boxVr=7dr;149 +boxbox=895;1bw +boxdL=7dh;13z +boxdR=7de;13w +boxdl=7bk;13m +boxdr=7bg;13l +boxh=7b4;13j +boxhD=7dx;14f +boxhU=7e0;14i +boxhd=7cc;13r +boxhu=7ck;13s +boxminus=6u7;xi +boxplus=6u6;xg +boxtimes=6u8;xk +boxuL=7dn;145 +boxuR=7dk;142 +boxul=7bs;13o +boxur=7bo;13n +boxv=7b6;13k +boxvH=7e2;14k +boxvL=7dt;14b +boxvR=7dq;148 +boxvh=7cs;13t +boxvl=7c4;13q +boxvr=7bw;13p +bprime=6d1;ej +breve=k8;83 +brvbar=4m;1k +bscr=2kl3;1jv +bsemi=6dr;er +bsim=6rh;qd +bsime=6vh;zq +bsol=2k;x +bsolb=891;1bv +bsolhsub=7uw;16r +bull=6ci;e9 +bullet=6ci;e8 +bump=6ry;rp +bumpE=8fi;1gu +bumpe=6rz;ru +bumpeq=6rz;rt +cacute=7b;4p +cap=6qx;pa +capand=8ck;1dq +capbrcup=8cp;1dv +capcap=8cr;1dx +capcup=8cn;1dt +capdot=8cg;1dn +caps=6qx,1e68;p9 +caret=6dd;eo +caron=jr;81 +ccaps=8ct;1dz +ccaron=7h;4v +ccedil=6f;3s +ccirc=7d;4r +ccups=8cs;1dy +ccupssm=8cw;1e0 +cdot=7f;4t +cedil=54;2f +cemptyv=88i;1bf +cent=4i;1g +centerdot=53;2c +cfr=2ko0;1l4 +chcy=uf;ce +check=7pv;16j +checkmark=7pv;16i +chi=qv;9s +cir=7gr;15q +cirE=88z;1bt +circ=jq;7z +circeq=6s7;sc +circlearrowleft=6nu;k6 +circlearrowright=6nv;k8 +circledR=4u;1w +circledS=79k;13g +circledast=6u3;xc +circledcirc=6u2;xa +circleddash=6u5;xe +cire=6s7;sd +cirfnint=8b4;1cq +cirmid=8hb;1j0 +cirscir=88y;1bs +clubs=7kz;168 +clubsuit=7kz;167 +colon=1m;j +colone=6s4;s7 +coloneq=6s4;s5 +comma=18;g +commat=1s;u +comp=6pt;mv +compfn=6qg;o7 +complement=6pt;mu +complexes=6iq;f6 +cong=6rp;qz +congdot=8dp;1ef +conint=6r2;pj +copf=2kpg;1md +coprod=6q8;nr +copy=4p;1r +copysr=6jb;fz +crarr=6np;k1 +cross=7pz;16k +cscr=2kl4;1jw +csub=8gf;1id +csube=8gh;1if +csup=8gg;1ie +csupe=8gi;1ig +ctdot=6wf;11g +cudarrl=854;18x +cudarrr=851;18u +cuepr=6vy;10m +cuesc=6vz;10o +cularr=6nq;k3 +cularrp=859;190 +cup=6qy;pc +cupbrcap=8co;1du +cupcap=8cm;1ds +cupcup=8cq;1dw +cupdot=6tp;we +cupor=8cl;1dr +cups=6qy,1e68;pb +curarr=6nr;k5 +curarrm=858;18z +curlyeqprec=6vy;10l +curlyeqsucc=6vz;10n +curlyvee=6vi;zr +curlywedge=6vj;zt +curren=4k;1i +curvearrowleft=6nq;k2 +curvearrowright=6nr;k4 +cuvee=6vi;zs +cuwed=6vj;zu +cwconint=6r6;pq +cwint=6r5;po +cylcty=6y5;12u +dArr=6oj;m2 +dHar=86d;19t +dagger=6cg;e5 +daleth=6k8;h7 +darr=6mr;ia +dash=6c0;dl +dashv=6ub;xr +dbkarow=83z;180 +dblac=kd;8b +dcaron=7j;4x +dcy=tw;bv +dd=6km;hb +ddagger=6ch;e6 +ddarr=6oa;ld +ddotseq=8dz;1ep +deg=4w;21 +delta=qc;95 +demptyv=88h;1be +dfisht=873;1aj +dfr=2ko1;1l5 +dharl=6o3;kx +dharr=6o2;ku +diam=6v8;zc +diamond=6v8;zb +diamondsuit=7l2;16b +diams=7l2;16c +die=4o;1o +digamma=rh;a6 +disin=6wi;11j +div=6v;49 +divide=6v;48 +divideontimes=6vb;zg +divonx=6vb;zh +djcy=uq;co +dlcorn=6xq;12n +dlcrop=6x9;12a +dollar=10;6 +dopf=2kph;1me +dot=k9;85 +doteq=6s0;rx +doteqdot=6s1;rz +dotminus=6rc;q2 +dotplus=6qc;ny +dotsquare=6u9;xm +doublebarwedge=6x2;11z +downarrow=6mr;i9 +downdownarrows=6oa;lc +downharpoonleft=6o3;kv +downharpoonright=6o2;ks +drbkarow=840;182 +drcorn=6xr;12p +drcrop=6x8;129 +dscr=2kl5;1jx +dscy=ut;cr +dsol=8ae;1cc +dstrok=7l;4z +dtdot=6wh;11i +dtri=7gf;15j +dtrif=7ge;15h +duarr=6ph;mo +duhar=86n;1a5 +dwangle=886;1b3 +dzcy=v3;d0 +dzigrarr=7wf;17r +eDDot=8dz;1eq +eDot=6s1;s0 +eacute=6h;3u +easter=8dq;1eg +ecaron=7v;57 +ecir=6s6;sb +ecirc=6i;3v +ecolon=6s5;s9 +ecy=ul;ck +edot=7r;53 +ee=6kn;he +efDot=6s2;s2 +efr=2ko2;1l6 +eg=8ey;1g9 +egrave=6g;3t +egs=8eu;1g5 +egsdot=8ew;1g7 +el=8ex;1g8 +elinters=73b;13e +ell=6j7;fv +els=8et;1g3 +elsdot=8ev;1g6 +emacr=7n;51 +empty=6px;n7 +emptyset=6px;n5 +emptyv=6px;n6 +emsp=6bn;d2 +emsp13=6bo;d3 +emsp14=6bp;d4 +eng=97;6h +ensp=6bm;d1 +eogon=7t;55 +eopf=2kpi;1mf +epar=6vp;103 +eparsl=89v;1c6 +eplus=8dt;1ek +epsi=qd;97 +epsilon=qd;96 +epsiv=s5;ae +eqcirc=6s6;sa +eqcolon=6s5;s8 +eqsim=6rm;qq +eqslantgtr=8eu;1g4 +eqslantless=8et;1g2 +equals=1p;p +equest=6sf;sj +equiv=6sh;so +equivDD=8e0;1er +eqvparsl=89x;1c8 +erDot=6s3;s4 +erarr=86p;1a7 +escr=6jz;gs +esdot=6s0;ry +esim=6rm;qr +eta=qf;99 +eth=6o;41 +euml=6j;3w +euro=6gc;f2 +excl=x;2 +exist=6pv;n0 +expectation=6k0;gt +exponentiale=6kn;hd +fallingdotseq=6s2;s1 +fcy=uc;cb +female=7k0;163 +ffilig=1dkz;1ja +fflig=1dkw;1j7 +ffllig=1dl0;1jb +ffr=2ko3;1l7 +filig=1dkx;1j8 +fjlig=2u,2y;15 +flat=7l9;16e +fllig=1dky;1j9 +fltns=7g1;153 +fnof=b6;7v +fopf=2kpj;1mg +forall=6ps;mt +fork=6vo;102 +forkv=8gp;1in +fpartint=8b1;1cp +frac12=59;2k +frac13=6kz;hh +frac14=58;2j +frac15=6l1;hj +frac16=6l5;hn +frac18=6l7;hp +frac23=6l0;hi +frac25=6l2;hk +frac34=5a;2m +frac35=6l3;hl +frac38=6l8;hq +frac45=6l4;hm +frac56=6l6;ho +frac58=6l9;hr +frac78=6la;hs +frasl=6dg;eq +frown=6xu;12r +fscr=2kl7;1jy +gE=6sn;t8 +gEl=8ek;1ft +gacute=dx;7x +gamma=qb;94 +gammad=rh;a7 +gap=8ee;1fh +gbreve=7z;5b +gcirc=7x;59 +gcy=tv;bu +gdot=81;5d +ge=6sl;sx +gel=6vv;10k +geq=6sl;sw +geqq=6sn;t7 +geqslant=8e6;1f6 +ges=8e6;1f7 +gescc=8fd;1gn +gesdot=8e8;1f9 +gesdoto=8ea;1fb +gesdotol=8ec;1fd +gesl=6vv,1e68;10h +gesles=8es;1g1 +gfr=2ko4;1l8 +gg=6sr;ts +ggg=6vt;10b +gimel=6k7;h6 +gjcy=ur;cp +gl=6t3;un +glE=8eq;1fz +gla=8f9;1gj +glj=8f8;1gi +gnE=6sp;tg +gnap=8ei;1fp +gnapprox=8ei;1fo +gne=8eg;1fl +gneq=8eg;1fk +gneqq=6sp;tf +gnsim=6w7;10y +gopf=2kpk;1mh +grave=2o;14 +gscr=6iy;f9 +gsim=6sz;ud +gsime=8em;1fv +gsiml=8eo;1fx +gt=1q;s +gtcc=8fb;1gl +gtcir=8e2;1et +gtdot=6vr;107 +gtlPar=87p;1aw +gtquest=8e4;1ev +gtrapprox=8ee;1fg +gtrarr=86w;1ad +gtrdot=6vr;106 +gtreqless=6vv;10j +gtreqqless=8ek;1fs +gtrless=6t3;um +gtrsim=6sz;uc +gvertneqq=6sp,1e68;td +gvnE=6sp,1e68;te +hArr=6ok;m5 +hairsp=6bu;da +half=59;2l +hamilt=6iz;fb +hardcy=ui;ch +harr=6ms;id +harrcir=85k;192 +harrw=6nh;js +hbar=6j3;fl +hcirc=85;5g +hearts=7l1;16a +heartsuit=7l1;169 +hellip=6cm;eb +hercon=6ux;yr +hfr=2ko5;1l9 +hksearow=84l;18i +hkswarow=84m;18k +hoarr=6pr;mr +homtht=6rf;q5 +hookleftarrow=6nd;jj +hookrightarrow=6ne;jl +hopf=2kpl;1mi +horbar=6c5;do +hscr=2kl9;1jz +hslash=6j3;fi +hstrok=87;5i +hybull=6df;ep +hyphen=6c0;dk +iacute=6l;3y +ic=6eb;f1 +icirc=6m;3z +icy=u0;bz +iecy=tx;bw +iexcl=4h;1f +iff=6ok;m6 +ifr=2ko6;1la +igrave=6k;3x +ii=6ko;hg +iiiint=8b0;1cn +iiint=6r1;pg +iinfin=89o;1c3 +iiota=6jt;gm +ijlig=8j;5t +imacr=8b;5m +image=6j5;fp +imagline=6j4;fm +imagpart=6j5;fo +imath=8h;5r +imof=6uv;yo +imped=c5;7w +in=6q0;nd +incare=6it;f8 +infin=6qm;of +infintie=89p;1c4 +inodot=8h;5q +int=6qz;pe +intcal=6uy;yt +integers=6jo;gh +intercal=6uy;ys +intlarhk=8bb;1cx +intprod=8cc;1dk +iocy=up;cn +iogon=8f;5o +iopf=2kpm;1mj +iota=qh;9b +iprod=8cc;1dl +iquest=5b;2n +iscr=2kla;1k0 +isin=6q0;nc +isinE=6wp;11r +isindot=6wl;11n +isins=6wk;11l +isinsv=6wj;11k +isinv=6q0;nb +it=6ea;ez +itilde=89;5k +iukcy=uu;cs +iuml=6n;40 +jcirc=8l;5v +jcy=u1;c0 +jfr=2ko7;1lb +jmath=fr;7y +jopf=2kpn;1mk +jscr=2klb;1k1 +jsercy=uw;cu +jukcy=us;cq +kappa=qi;9c +kappav=s0;a9 +kcedil=8n;5x +kcy=u2;c1 +kfr=2ko8;1lc +kgreen=8o;5y +khcy=ud;cc +kjcy=v0;cy +kopf=2kpo;1ml +kscr=2klc;1k2 +lAarr=6oq;mf +lArr=6og;ls +lAtail=84b;18a +lBarr=83y;17z +lE=6sm;t2 +lEg=8ej;1fr +lHar=86a;19q +lacute=8q;60 +laemptyv=88k;1bh +lagran=6j6;ft +lambda=qj;9d +lang=7vs;16z +langd=87l;1as +langle=7vs;16y +lap=8ed;1ff +laquo=4r;1t +larr=6mo;hx +larrb=6p0;mk +larrbfs=84f;18e +larrfs=84d;18c +larrhk=6nd;jk +larrlp=6nf;jo +larrpl=855;18y +larrsim=86r;1a9 +larrtl=6n6;j7 +lat=8ff;1gp +latail=849;188 +late=8fh;1gt +lates=8fh,1e68;1gs +lbarr=83w;17w +lbbrk=7si;16p +lbrace=3f;16 +lbrack=2j;v +lbrke=87f;1am +lbrksld=87j;1aq +lbrkslu=87h;1ao +lcaron=8u;64 +lcedil=8s;62 +lceil=6x4;122 +lcub=3f;17 +lcy=u3;c2 +ldca=852;18v +ldquo=6cc;dz +ldquor=6ce;e3 +ldrdhar=86f;19v +ldrushar=85n;195 +ldsh=6nm;jz +le=6sk;st +leftarrow=6mo;hv +leftarrowtail=6n6;j6 +leftharpoondown=6nx;kd +leftharpoonup=6nw;ka +leftleftarrows=6o7;l6 +leftrightarrow=6ms;ic +leftrightarrows=6o6;l4 +leftrightharpoons=6ob;lf +leftrightsquigarrow=6nh;jr +leftthreetimes=6vf;zl +leg=6vu;10g +leq=6sk;ss +leqq=6sm;t1 +leqslant=8e5;1f0 +les=8e5;1f1 +lescc=8fc;1gm +lesdot=8e7;1f8 +lesdoto=8e9;1fa +lesdotor=8eb;1fc +lesg=6vu,1e68;10d +lesges=8er;1g0 +lessapprox=8ed;1fe +lessdot=6vq;104 +lesseqgtr=6vu;10f +lesseqqgtr=8ej;1fq +lessgtr=6t2;uj +lesssim=6sy;u9 +lfisht=870;1ag +lfloor=6x6;126 +lfr=2ko9;1ld +lg=6t2;uk +lgE=8ep;1fy +lhard=6nx;kf +lharu=6nw;kc +lharul=86i;19y +lhblk=7es;14o +ljcy=ux;cv +ll=6sq;tm +llarr=6o7;l7 +llcorner=6xq;12m +llhard=86j;19z +lltri=7i2;15w +lmidot=8w;66 +lmoust=71s;131 +lmoustache=71s;130 +lnE=6so;tc +lnap=8eh;1fn +lnapprox=8eh;1fm +lne=8ef;1fj +lneq=8ef;1fi +lneqq=6so;tb +lnsim=6w6;10x +loang=7vw;175 +loarr=6pp;mp +lobrk=7vq;16u +longleftarrow=7w5;178 +longleftrightarrow=7w7;17e +longmapsto=7wc;17p +longrightarrow=7w6;17b +looparrowleft=6nf;jn +looparrowright=6ng;jp +lopar=879;1ak +lopf=2kpp;1mm +loplus=8bx;1d6 +lotimes=8c4;1dc +lowast=6qf;o5 +lowbar=2n;12 +loz=7gq;15p +lozenge=7gq;15o +lozf=8a3;1ca +lpar=14;b +lparlt=87n;1au +lrarr=6o6;l5 +lrcorner=6xr;12o +lrhar=6ob;lg +lrhard=86l;1a1 +lrm=6by;di +lrtri=6v3;yx +lsaquo=6d5;ek +lscr=2kld;1k3 +lsh=6nk;jw +lsim=6sy;ua +lsime=8el;1fu +lsimg=8en;1fw +lsqb=2j;w +lsquo=6c8;ds +lsquor=6ca;dw +lstrok=8y;68 +lt=1o;n +ltcc=8fa;1gk +ltcir=8e1;1es +ltdot=6vq;105 +lthree=6vf;zm +ltimes=6vd;zj +ltlarr=86u;1ac +ltquest=8e3;1eu +ltrPar=87q;1ax +ltri=7gj;15n +ltrie=6us;yi +ltrif=7gi;15l +lurdshar=85m;194 +luruhar=86e;19u +lvertneqq=6so,1e68;t9 +lvnE=6so,1e68;ta +mDDot=6re;q4 +macr=4v;20 +male=7k2;164 +malt=7q8;16m +maltese=7q8;16l +map=6na;jg +mapsto=6na;jf +mapstodown=6nb;ji +mapstoleft=6n8;jb +mapstoup=6n9;jd +marker=7fy;152 +mcomma=8bt;1d4 +mcy=u4;c3 +mdash=6c4;dn +measuredangle=6qp;ok +mfr=2koa;1le +mho=6jr;gj +micro=51;29 +mid=6qr;oq +midast=16;d +midcir=8hc;1j1 +middot=53;2d +minus=6qa;nu +minusb=6u7;xj +minusd=6rc;q3 +minusdu=8bu;1d5 +mlcp=8gr;1ip +mldr=6cm;ec +mnplus=6qb;nw +models=6uf;xy +mopf=2kpq;1mn +mp=6qb;nx +mscr=2kle;1k4 +mstpos=6ri;qf +mu=qk;9e +multimap=6uw;yp +mumap=6uw;yq +nGg=6vt,mw;10a +nGt=6sr,6he;tp +nGtv=6sr,mw;to +nLeftarrow=6od;lk +nLeftrightarrow=6oe;lm +nLl=6vs,mw;108 +nLt=6sq,6he;tj +nLtv=6sq,mw;ti +nRightarrow=6of;lo +nVDash=6un;y7 +nVdash=6um;y6 +nabla=6pz;n8 +nacute=90;6a +nang=6qo,6he;oh +nap=6rt;rb +napE=8ds,mw;1ei +napid=6rv,mw;re +napos=95;6f +napprox=6rt;ra +natur=7la;16g +natural=7la;16f +naturals=6j9;fw +nbsp=4g;1e +nbump=6ry,mw;rm +nbumpe=6rz,mw;rr +ncap=8cj;1dp +ncaron=94;6e +ncedil=92;6c +ncong=6rr;r2 +ncongdot=8dp,mw;1ee +ncup=8ci;1do +ncy=u5;c4 +ndash=6c3;dm +ne=6sg;sl +neArr=6on;mb +nearhk=84k;18h +nearr=6mv;im +nearrow=6mv;il +nedot=6s0,mw;rv +nequiv=6si;sq +nesear=84o;18n +nesim=6rm,mw;qo +nexist=6pw;n3 +nexists=6pw;n2 +nfr=2kob;1lf +ngE=6sn,mw;t4 +nge=6sx;u7 +ngeq=6sx;u6 +ngeqq=6sn,mw;t5 +ngeqslant=8e6,mw;1f3 +nges=8e6,mw;1f4 +ngsim=6t1;uh +ngt=6sv;u1 +ngtr=6sv;u0 +nhArr=6oe;ln +nharr=6ni;ju +nhpar=8he;1j3 +ni=6q3;nk +nis=6ws;11u +nisd=6wq;11s +niv=6q3;nj +njcy=uy;cw +nlArr=6od;ll +nlE=6sm,mw;sy +nlarr=6my;iu +nldr=6cl;ea +nle=6sw;u4 +nleftarrow=6my;it +nleftrightarrow=6ni;jt +nleq=6sw;u3 +nleqq=6sm,mw;sz +nleqslant=8e5,mw;1ex +nles=8e5,mw;1ey +nless=6su;tx +nlsim=6t0;uf +nlt=6su;ty +nltri=6wa;115 +nltrie=6wc;11b +nmid=6qs;ou +nopf=2kpr;1mo +not=4s;1u +notin=6q1;ng +notinE=6wp,mw;11q +notindot=6wl,mw;11m +notinva=6q1;nf +notinvb=6wn;11p +notinvc=6wm;11o +notni=6q4;nn +notniva=6q4;nm +notnivb=6wu;11w +notnivc=6wt;11v +npar=6qu;p4 +nparallel=6qu;p2 +nparsl=8hp,6hx;1j5 +npart=6pu,mw;mw +npolint=8b8;1cu +npr=6tc;vd +nprcue=6w0;10q +npre=8fj,mw;1gw +nprec=6tc;vc +npreceq=8fj,mw;1gx +nrArr=6of;lp +nrarr=6mz;iw +nrarrc=84z,mw;18s +nrarrw=6n1,mw;ix +nrightarrow=6mz;iv +nrtri=6wb;118 +nrtrie=6wd;11e +nsc=6td;vg +nsccue=6w1;10s +nsce=8fk,mw;1h2 +nscr=2klf;1k5 +nshortmid=6qs;os +nshortparallel=6qu;p1 +nsim=6rl;qm +nsime=6ro;qx +nsimeq=6ro;qw +nsmid=6qs;ot +nspar=6qu;p3 +nsqsube=6w2;10u +nsqsupe=6w3;10w +nsub=6tg;vs +nsubE=8g5,mw;1hv +nsube=6tk;w2 +nsubset=6te,6he;vi +nsubseteq=6tk;w1 +nsubseteqq=8g5,mw;1hw +nsucc=6td;vf +nsucceq=8fk,mw;1h3 +nsup=6th;vt +nsupE=8g6,mw;1hz +nsupe=6tl;w5 +nsupset=6tf,6he;vn +nsupseteq=6tl;w4 +nsupseteqq=8g6,mw;1i0 +ntgl=6t5;ur +ntilde=6p;42 +ntlg=6t4;up +ntriangleleft=6wa;114 +ntrianglelefteq=6wc;11a +ntriangleright=6wb;117 +ntrianglerighteq=6wd;11d +nu=ql;9f +num=z;5 +numero=6ja;fy +numsp=6br;d5 +nvDash=6ul;y5 +nvHarr=83o;17u +nvap=6rx,6he;ri +nvdash=6uk;y4 +nvge=6sl,6he;su +nvgt=1q,6he;q +nvinfin=89q;1c5 +nvlArr=83m;17s +nvle=6sk,6he;sr +nvlt=1o,6he;l +nvltrie=6us,6he;yf +nvrArr=83n;17t +nvrtrie=6ut,6he;yj +nvsim=6rg,6he;q6 +nwArr=6om;ma +nwarhk=84j;18g +nwarr=6mu;ij +nwarrow=6mu;ii +nwnear=84n;18m +oS=79k;13h +oacute=6r;44 +oast=6u3;xd +ocir=6u2;xb +ocirc=6s;45 +ocy=u6;c5 +odash=6u5;xf +odblac=9d;6l +odiv=8c8;1dg +odot=6u1;x9 +odsold=88s;1bn +oelig=9f;6n +ofcir=88v;1bp +ofr=2koc;1lg +ogon=kb;87 +ograve=6q;43 +ogt=88x;1br +ohbar=88l;1bi +ohm=q1;91 +oint=6r2;pk +olarr=6nu;k7 +olcir=88u;1bo +olcross=88r;1bm +oline=6da;en +olt=88w;1bq +omacr=99;6j +omega=qx;9u +omicron=qn;9h +omid=88m;1bj +ominus=6ty;x4 +oopf=2kps;1mp +opar=88n;1bk +operp=88p;1bl +oplus=6tx;x2 +or=6qw;p8 +orarr=6nv;k9 +ord=8d9;1ea +order=6k4;h1 +orderof=6k4;h0 +ordf=4q;1s +ordm=56;2h +origof=6uu;yn +oror=8d2;1e4 +orslope=8d3;1e5 +orv=8d7;1e8 +oscr=6k4;h2 +oslash=6w;4a +osol=6u0;x7 +otilde=6t;46 +otimes=6tz;x6 +otimesas=8c6;1de +ouml=6u;47 +ovbar=6yl;12x +par=6qt;oz +para=52;2a +parallel=6qt;ox +parsim=8hf;1j4 +parsl=8hp;1j6 +part=6pu;my +pcy=u7;c6 +percnt=11;7 +period=1a;h +permil=6cw;ed +perp=6ud;xw +pertenk=6cx;ee +pfr=2kod;1lh +phi=qu;9r +phiv=r9;a2 +phmmat=6k3;gy +phone=7im;162 +pi=qo;9i +pitchfork=6vo;101 +piv=ra;a4 +planck=6j3;fj +planckh=6j2;fh +plankv=6j3;fk +plus=17;f +plusacir=8bn;1cz +plusb=6u6;xh +pluscir=8bm;1cy +plusdo=6qc;nz +plusdu=8bp;1d1 +pluse=8du;1el +plusmn=4x;23 +plussim=8bq;1d2 +plustwo=8br;1d3 +pm=4x;24 +pointint=8b9;1cv +popf=2kpt;1mq +pound=4j;1h +pr=6t6;uu +prE=8fn;1h7 +prap=8fr;1he +prcue=6t8;v0 +pre=8fj;1h0 +prec=6t6;ut +precapprox=8fr;1hd +preccurlyeq=6t8;uz +preceq=8fj;1gz +precnapprox=8ft;1hh +precneqq=8fp;1h9 +precnsim=6w8;10z +precsim=6ta;v5 +prime=6cy;ef +primes=6jd;g2 +prnE=8fp;1ha +prnap=8ft;1hi +prnsim=6w8;110 +prod=6q7;np +profalar=6y6;12v +profline=6xe;12e +profsurf=6xf;12f +prop=6ql;oe +propto=6ql;oc +prsim=6ta;v6 +prurel=6uo;y8 +pscr=2klh;1k6 +psi=qw;9t +puncsp=6bs;d6 +qfr=2koe;1li +qint=8b0;1co +qopf=2kpu;1mr +qprime=6dz;es +qscr=2kli;1k7 +quaternions=6j1;ff +quatint=8ba;1cw +quest=1r;t +questeq=6sf;si +quot=y;4 +rAarr=6or;mh +rArr=6oi;lz +rAtail=84c;18b +rBarr=83z;181 +rHar=86c;19s +race=6rh,mp;qb +racute=9h;6p +radic=6qi;o8 +raemptyv=88j;1bg +rang=7vt;172 +rangd=87m;1at +range=885;1b2 +rangle=7vt;171 +raquo=57;2i +rarr=6mq;i6 +rarrap=86t;1ab +rarrb=6p1;mm +rarrbfs=84g;18f +rarrc=84z;18t +rarrfs=84e;18d +rarrhk=6ne;jm +rarrlp=6ng;jq +rarrpl=85h;191 +rarrsim=86s;1aa +rarrtl=6n7;j9 +rarrw=6n1;iz +ratail=84a;189 +ratio=6ra;pz +rationals=6je;g4 +rbarr=83x;17y +rbbrk=7sj;16q +rbrace=3h;1b +rbrack=2l;y +rbrke=87g;1an +rbrksld=87i;1ap +rbrkslu=87k;1ar +rcaron=9l;6t +rcedil=9j;6r +rceil=6x5;124 +rcub=3h;1c +rcy=u8;c7 +rdca=853;18w +rdldhar=86h;19x +rdquo=6cd;e2 +rdquor=6cd;e1 +rdsh=6nn;k0 +real=6jg;g9 +realine=6jf;g6 +realpart=6jg;g8 +reals=6jh;gc +rect=7fx;151 +reg=4u;1y +rfisht=871;1ah +rfloor=6x7;128 +rfr=2kof;1lj +rhard=6o1;kr +rharu=6o0;ko +rharul=86k;1a0 +rho=qp;9j +rhov=s1;ab +rightarrow=6mq;i4 +rightarrowtail=6n7;j8 +rightharpoondown=6o1;kp +rightharpoonup=6o0;km +rightleftarrows=6o4;kz +rightleftharpoons=6oc;lh +rightrightarrows=6o9;la +rightsquigarrow=6n1;iy +rightthreetimes=6vg;zn +ring=ka;86 +risingdotseq=6s3;s3 +rlarr=6o4;l0 +rlhar=6oc;lj +rlm=6bz;dj +rmoust=71t;133 +rmoustache=71t;132 +rnmid=8ha;1iz +roang=7vx;176 +roarr=6pq;mq +robrk=7vr;16w +ropar=87a;1al +ropf=2kpv;1ms +roplus=8by;1d7 +rotimes=8c5;1dd +rpar=15;c +rpargt=87o;1av +rppolint=8b6;1cs +rrarr=6o9;lb +rsaquo=6d6;el +rscr=2klj;1k8 +rsh=6nl;jy +rsqb=2l;z +rsquo=6c9;dv +rsquor=6c9;du +rthree=6vg;zo +rtimes=6ve;zk +rtri=7g9;15d +rtrie=6ut;ym +rtrif=7g8;15b +rtriltri=89a;1by +ruluhar=86g;19w +rx=6ji;ge +sacute=9n;6v +sbquo=6ca;dx +sc=6t7;ux +scE=8fo;1h8 +scap=8fs;1hg +scaron=9t;71 +sccue=6t9;v3 +sce=8fk;1h6 +scedil=9r;6z +scirc=9p;6x +scnE=8fq;1hc +scnap=8fu;1hk +scnsim=6w9;112 +scpolint=8b7;1ct +scsim=6tb;va +scy=u9;c8 +sdot=6v9;zd +sdotb=6u9;xn +sdote=8di;1ec +seArr=6oo;mc +searhk=84l;18j +searr=6mw;ip +searrow=6mw;io +sect=4n;1l +semi=1n;k +seswar=84p;18p +setminus=6qe;o2 +setmn=6qe;o4 +sext=7qu;16n +sfr=2kog;1lk +sfrown=6xu;12q +sharp=7lb;16h +shchcy=uh;cg +shcy=ug;cf +shortmid=6qr;oo +shortparallel=6qt;ow +shy=4t;1v +sigma=qr;9n +sigmaf=qq;9l +sigmav=qq;9m +sim=6rg;qa +simdot=8dm;1ed +sime=6rn;qu +simeq=6rn;qt +simg=8f2;1gb +simgE=8f4;1gd +siml=8f1;1ga +simlE=8f3;1gc +simne=6rq;r0 +simplus=8bo;1d0 +simrarr=86q;1a8 +slarr=6mo;hw +smallsetminus=6qe;o0 +smashp=8c3;1db +smeparsl=89w;1c7 +smid=6qr;op +smile=6xv;12t +smt=8fe;1go +smte=8fg;1gr +smtes=8fg,1e68;1gq +softcy=uk;cj +sol=1b;i +solb=890;1bu +solbar=6yn;12y +sopf=2kpw;1mt +spades=7kw;166 +spadesuit=7kw;165 +spar=6qt;oy +sqcap=6tv;wx +sqcaps=6tv,1e68;wv +sqcup=6tw;x0 +sqcups=6tw,1e68;wy +sqsub=6tr;wk +sqsube=6tt;wr +sqsubset=6tr;wj +sqsubseteq=6tt;wq +sqsup=6ts;wo +sqsupe=6tu;wu +sqsupset=6ts;wn +sqsupseteq=6tu;wt +squ=7fl;14v +square=7fl;14u +squarf=7fu;14y +squf=7fu;14z +srarr=6mq;i5 +sscr=2klk;1k9 +ssetmn=6qe;o3 +ssmile=6xv;12s +sstarf=6va;ze +star=7ie;161 +starf=7id;160 +straightepsilon=s5;ac +straightphi=r9;a0 +strns=4v;1z +sub=6te;vl +subE=8g5;1hy +subdot=8fx;1hn +sube=6ti;vw +subedot=8g3;1ht +submult=8g1;1hr +subnE=8gb;1i8 +subne=6tm;w9 +subplus=8fz;1hp +subrarr=86x;1ae +subset=6te;vk +subseteq=6ti;vv +subseteqq=8g5;1hx +subsetneq=6tm;w8 +subsetneqq=8gb;1i7 +subsim=8g7;1i3 +subsub=8gl;1ij +subsup=8gj;1ih +succ=6t7;uw +succapprox=8fs;1hf +succcurlyeq=6t9;v2 +succeq=8fk;1h5 +succnapprox=8fu;1hj +succneqq=8fq;1hb +succnsim=6w9;111 +succsim=6tb;v9 +sum=6q9;nt +sung=7l6;16d +sup=6tf;vr +sup1=55;2g +sup2=4y;25 +sup3=4z;26 +supE=8g6;1i2 +supdot=8fy;1ho +supdsub=8go;1im +supe=6tj;vz +supedot=8g4;1hu +suphsol=7ux;16s +suphsub=8gn;1il +suplarr=86z;1af +supmult=8g2;1hs +supnE=8gc;1ic +supne=6tn;wd +supplus=8g0;1hq +supset=6tf;vq +supseteq=6tj;vy +supseteqq=8g6;1i1 +supsetneq=6tn;wc +supsetneqq=8gc;1ib +supsim=8g8;1i4 +supsub=8gk;1ii +supsup=8gm;1ik +swArr=6op;md +swarhk=84m;18l +swarr=6mx;is +swarrow=6mx;ir +swnwar=84q;18r +szlig=67;3k +target=6xi;12h +tau=qs;9o +tbrk=71w;135 +tcaron=9x;75 +tcedil=9v;73 +tcy=ua;c9 +tdot=6hn;f4 +telrec=6xh;12g +tfr=2koh;1ll +there4=6r8;pv +therefore=6r8;pu +theta=qg;9a +thetasym=r5;9v +thetav=r5;9x +thickapprox=6rs;r3 +thicksim=6rg;q7 +thinsp=6bt;d8 +thkap=6rs;r7 +thksim=6rg;q8 +thorn=72;4g +tilde=kc;89 +times=5z;3c +timesb=6u8;xl +timesbar=8c1;1da +timesd=8c0;1d9 +tint=6r1;ph +toea=84o;18o +top=6uc;xt +topbot=6ye;12w +topcir=8hd;1j2 +topf=2kpx;1mu +topfork=8gq;1io +tosa=84p;18q +tprime=6d0;eh +trade=6jm;gg +triangle=7g5;158 +triangledown=7gf;15i +triangleleft=7gj;15m +trianglelefteq=6us;yh +triangleq=6sc;sg +triangleright=7g9;15c +trianglerighteq=6ut;yl +tridot=7ho;15r +trie=6sc;sh +triminus=8ca;1di +triplus=8c9;1dh +trisb=899;1bx +tritime=8cb;1dj +trpezium=736;13d +tscr=2kll;1ka +tscy=ue;cd +tshcy=uz;cx +tstrok=9z;77 +twixt=6ss;tu +twoheadleftarrow=6n2;j0 +twoheadrightarrow=6n4;j3 +uArr=6oh;lv +uHar=86b;19r +uacute=6y;4c +uarr=6mp;i1 +ubrcy=v2;cz +ubreve=a5;7d +ucirc=6z;4d +ucy=ub;ca +udarr=6o5;l2 +udblac=a9;7h +udhar=86m;1a3 +ufisht=872;1ai +ufr=2koi;1lm +ugrave=6x;4b +uharl=6nz;kl +uharr=6ny;ki +uhblk=7eo;14n +ulcorn=6xo;12j +ulcorner=6xo;12i +ulcrop=6xb;12c +ultri=7i0;15u +umacr=a3;7b +uml=4o;1p +uogon=ab;7j +uopf=2kpy;1mv +uparrow=6mp;i0 +updownarrow=6mt;if +upharpoonleft=6nz;kj +upharpoonright=6ny;kg +uplus=6tq;wg +upsi=qt;9q +upsih=r6;9y +upsilon=qt;9p +upuparrows=6o8;l8 +urcorn=6xp;12l +urcorner=6xp;12k +urcrop=6xa;12b +uring=a7;7f +urtri=7i1;15v +uscr=2klm;1kb +utdot=6wg;11h +utilde=a1;79 +utri=7g5;159 +utrif=7g4;157 +uuarr=6o8;l9 +uuml=70;4e +uwangle=887;1b4 +vArr=6ol;m9 +vBar=8h4;1iu +vBarv=8h5;1iv +vDash=6ug;y0 +vangrt=87w;1az +varepsilon=s5;ad +varkappa=s0;a8 +varnothing=6px;n4 +varphi=r9;a1 +varpi=ra;a3 +varpropto=6ql;ob +varr=6mt;ig +varrho=s1;aa +varsigma=qq;9k +varsubsetneq=6tm,1e68;w6 +varsubsetneqq=8gb,1e68;1i5 +varsupsetneq=6tn,1e68;wa +varsupsetneqq=8gc,1e68;1i9 +vartheta=r5;9w +vartriangleleft=6uq;y9 +vartriangleright=6ur;yc +vcy=tu;bt +vdash=6ua;xp +vee=6qw;p7 +veebar=6uz;yu +veeeq=6sa;sf +vellip=6we;11f +verbar=3g;19 +vert=3g;1a +vfr=2koj;1ln +vltri=6uq;yb +vnsub=6te,6he;vj +vnsup=6tf,6he;vo +vopf=2kpz;1mw +vprop=6ql;od +vrtri=6ur;ye +vscr=2kln;1kc +vsubnE=8gb,1e68;1i6 +vsubne=6tm,1e68;w7 +vsupnE=8gc,1e68;1ia +vsupne=6tn,1e68;wb +vzigzag=87u;1ay +wcirc=ad;7l +wedbar=8db;1eb +wedge=6qv;p5 +wedgeq=6s9;se +weierp=6jc;g0 +wfr=2kok;1lo +wopf=2kq0;1mx +wp=6jc;g1 +wr=6rk;qk +wreath=6rk;qj +wscr=2klo;1kd +xcap=6v6;z6 +xcirc=7hr;15t +xcup=6v7;z9 +xdtri=7gd;15f +xfr=2kol;1lp +xhArr=7wa;17o +xharr=7w7;17f +xi=qm;9g +xlArr=7w8;17i +xlarr=7w5;179 +xmap=7wc;17q +xnis=6wr;11t +xodot=8ao;1ce +xopf=2kq1;1my +xoplus=8ap;1cg +xotime=8aq;1ci +xrArr=7w9;17l +xrarr=7w6;17c +xscr=2klp;1ke +xsqcup=8au;1cm +xuplus=8as;1ck +xutri=7g3;155 +xvee=6v5;z2 +xwedge=6v4;yz +yacute=71;4f +yacy=un;cm +ycirc=af;7n +ycy=uj;ci +yen=4l;1j +yfr=2kom;1lq +yicy=uv;ct +yopf=2kq2;1mz +yscr=2klq;1kf +yucy=um;cl +yuml=73;4h +zacute=ai;7q +zcaron=am;7u +zcy=tz;by +zdot=ak;7s +zeetrf=6js;gk +zeta=qe;98 +zfr=2kon;1lr +zhcy=ty;bx +zigrarr=6ot;mi +zopf=2kq3;1n0 +zscr=2klr;1kg +zwj=6bx;dh +zwnj=6bw;dg diff --git a/src/main/java/org/jsoup/nodes/entities-xhtml.properties b/src/main/java/org/jsoup/nodes/entities-xhtml.properties new file mode 100644 index 0000000000..6631d1aee7 --- /dev/null +++ b/src/main/java/org/jsoup/nodes/entities-xhtml.properties @@ -0,0 +1,4 @@ +amp=12;1 +gt=1q;3 +lt=1o;2 +quot=y;0 diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 3ea0d2d736..3b110c9125 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -181,6 +181,13 @@ final void appendAttributeValue(char[] append) { ensureAttributeValue(); pendingAttributeValue.append(append); } + + final void appendAttributeValue(int[] appendCodepoints) { + ensureAttributeValue(); + for (int codepoint : appendCodepoints) { + pendingAttributeValue.appendCodePoint(codepoint); + } + } final void setEmptyAttributeValue() { hasEmptyAttributeValue = true; diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index b62e293441..b793c38780 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -101,6 +101,10 @@ void emit(char[] chars) { emit(String.valueOf(chars)); } + void emit(int[] codepoints) { + emit(new String(codepoints, 0, codepoints.length)); + } + void emit(char c) { emit(String.valueOf(c)); } @@ -122,8 +126,9 @@ void acknowledgeSelfClosingFlag() { selfClosingFlagAcknowledged = true; } - final private char[] charRefHolder = new char[1]; // holder to not have to keep creating arrays - char[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { + final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays + final private int[] multipointHolder = new int[2]; + int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { if (reader.isEmpty()) return null; if (additionalAllowedCharacter != null && additionalAllowedCharacter == reader.current()) @@ -131,7 +136,7 @@ char[] consumeCharacterReference(Character additionalAllowedCharacter, boolean i if (reader.matchesAnySorted(notCharRefCharsSorted)) return null; - final char[] charRef = charRefHolder; + final int[] codeRef = codepointHolder; reader.mark(); if (reader.matchConsume("#")) { // numbered boolean isHexMode = reader.matchConsumeIgnoreCase("X"); @@ -151,16 +156,13 @@ char[] consumeCharacterReference(Character additionalAllowedCharacter, boolean i } // skip if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) { characterReferenceError("character outside of valid range"); - charRef[0] = replacementChar; - return charRef; + codeRef[0] = replacementChar; + return codeRef; } else { // todo: implement number replacement table // todo: check for extra illegal unicode points as parse errors - if (charval < Character.MIN_SUPPLEMENTARY_CODE_POINT) { - charRef[0] = (char) charval; - return charRef; - } else - return Character.toChars(charval); + codeRef[0] = charval; + return codeRef; } } else { // named // get as many letters as possible, and look for matching entities. @@ -182,8 +184,16 @@ char[] consumeCharacterReference(Character additionalAllowedCharacter, boolean i } if (!reader.matchConsume(";")) characterReferenceError("missing semicolon"); // missing semi - charRef[0] = Entities.getCharacterByName(nameRef); - return charRef; + int numChars = Entities.codepointsForName(nameRef, multipointHolder); + if (numChars == 1) { + codeRef[0] = multipointHolder[0]; + return codeRef; + } else if (numChars ==2) { + return multipointHolder; + } else { + Validate.fail("Unexpected characters returned for " + nameRef); + return multipointHolder; + } } } @@ -265,11 +275,15 @@ String unescapeEntities(boolean inAttribute) { builder.append(reader.consumeTo('&')); if (reader.matches('&')) { reader.consume(); - char[] c = consumeCharacterReference(null, inAttribute); + int[] c = consumeCharacterReference(null, inAttribute); if (c == null || c.length==0) builder.append('&'); - else - builder.append(c); + else { + builder.appendCodePoint(c[0]); + if (c.length == 2) + builder.appendCodePoint(c[1]); + } + } } return builder.toString(); diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index c10dd2c1c5..3a2ac1f9e4 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -737,7 +737,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(AfterAttributeValue_quoted); break; case '&': - char[] ref = t.consumeCharacterReference('"', true); + int[] ref = t.consumeCharacterReference('"', true); if (ref != null) t.tagPending.appendAttributeValue(ref); else @@ -769,7 +769,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(AfterAttributeValue_quoted); break; case '&': - char[] ref = t.consumeCharacterReference('\'', true); + int[] ref = t.consumeCharacterReference('\'', true); if (ref != null) t.tagPending.appendAttributeValue(ref); else @@ -803,7 +803,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(BeforeAttributeName); break; case '&': - char[] ref = t.consumeCharacterReference('>', true); + int[] ref = t.consumeCharacterReference('>', true); if (ref != null) t.tagPending.appendAttributeValue(ref); else @@ -1680,7 +1680,7 @@ private static void readData(Tokeniser t, CharacterReader r, TokeniserState curr } private static void readCharRef(Tokeniser t, TokeniserState advance) { - char[] c = t.consumeCharacterReference(null, false); + int[] c = t.consumeCharacterReference(null, false); if (c == null) t.emit('&'); else diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 29bb9d17bb..d212950f58 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -36,7 +36,7 @@ public class UrlConnectTest { private static final String WEBSITE_WITH_INVALID_CERTIFICATE = "https://certs.cac.washington.edu/CAtest/"; private static final String WEBSITE_WITH_SNI = "https://jsoup.org/"; private static String echoURL = "http://direct.infohound.net/tools/q.pl"; - private static String browserUa = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"; + public static String browserUa = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"; @Test public void fetchURl() throws IOException { diff --git a/src/test/java/org/jsoup/nodes/BuildEntities.java b/src/test/java/org/jsoup/nodes/BuildEntities.java new file mode 100644 index 0000000000..694a1a90b2 --- /dev/null +++ b/src/test/java/org/jsoup/nodes/BuildEntities.java @@ -0,0 +1,136 @@ +package org.jsoup.nodes; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.jsoup.Connection; +import org.jsoup.Jsoup; +import org.jsoup.integration.UrlConnectTest; +import org.jsoup.nodes.Entities; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; + +/** + * Fetches HTML entity names from w3.org json, and outputs data files for optimized used in Entities. + * I refuse to believe that entity names like "NotNestedLessLess" are valuable or useful for HTML authors. Implemented + * only to be complete. + */ +class BuildEntities { + private static final String projectDir = "/Users/jhy/projects/jsoup"; + + public static void main(String[] args) throws IOException { + String url = "https://www.w3.org/TR/2012/WD-html5-20121025/entities.json"; + Connection.Response res = Jsoup.connect(url) + .ignoreContentType(true) + .userAgent(UrlConnectTest.browserUa) + .execute(); + + Gson gson = new Gson(); + Map input = gson.fromJson(res.body(), + new TypeToken>() { + }.getType()); + + + // build name sorted base and full character lists: + ArrayList base = new ArrayList(); + ArrayList full = new ArrayList(); + + for (Map.Entry entry : input.entrySet()) { + String name = entry.getKey().substring(1); // name is like ´ or ´ , trim & + CharacterRef ref = entry.getValue(); + if (name.endsWith(";")) { + name = name.substring(0, name.length() - 1); + full.add(ref); + } else { + base.add(ref); + } + ref.name = name; + } + Collections.sort(base, byName); + Collections.sort(full, byName); + + // now determine code point order + ArrayList baseByCode = new ArrayList(base); + ArrayList fullByCode = new ArrayList(full); + Collections.sort(baseByCode, byCode); + Collections.sort(fullByCode, byCode); + + // and update their codepoint index. Don't + ArrayList[] codelists = new ArrayList[]{baseByCode, fullByCode}; + for (ArrayList codelist : codelists) { + for (int i = 0; i < codelist.size(); i++) { + codelist.get(i).codeIndex = i; + } + } + + // now write them + persist("entities-full.properties", full); + persist("entities-base.properties", base); + + System.out.println("Full size: " + full.size() + ", base size: " + base.size()); + } + + private static void persist(String name, ArrayList refs) throws IOException { + String base = projectDir + "/src/main/java/org/jsoup/nodes"; + File file = new File(base, name); + FileWriter writer = new FileWriter(file, false); + for (CharacterRef ref : refs) { + writer.append(ref.toString()).append("\n"); + } + writer.close(); + } + + + private static class CharacterRef { + int[] codepoints; + String name; + int codeIndex; + + @Override + public String toString() { + return name + + "=" + + d(codepoints[0]) + + (codepoints.length > 1 ? "," + d(codepoints[1]) : "") + + ";" + d(codeIndex); + } + } + + private static String d(int d) { + return Integer.toString(d, Entities.codepointRadix); + } + + private static class ByName implements Comparator { + public int compare(CharacterRef o1, CharacterRef o2) { + return o1.name.compareTo(o2.name); + } + } + + private static class ByCode implements Comparator { + public int compare(CharacterRef o1, CharacterRef o2) { + int[] c1 = o1.codepoints; + int[] c2 = o2.codepoints; + int first = c1[0] - c2[0]; + if (first != 0) + return first; + if (c1.length == 1 && c2.length == 1) { // for the same code, use the shorter name + int len = o2.name.length() - o1.name.length(); + if (len != 0) + return len; + return o1.name.compareTo(o2.name); + } + if (c1.length == 2 && c2.length == 2) + return c1[1] - c2[1]; + else + return c2.length - c1.length; // pushes multi down the list so hits on singles first (don't support multi lookup by codepoint yet) + } + } + + private static ByName byName = new ByName(); + private static ByCode byCode = new ByCode(); +} diff --git a/src/test/java/org/jsoup/nodes/EntitiesTest.java b/src/test/java/org/jsoup/nodes/EntitiesTest.java index 9a648d53c7..c7ba4247a9 100644 --- a/src/test/java/org/jsoup/nodes/EntitiesTest.java +++ b/src/test/java/org/jsoup/nodes/EntitiesTest.java @@ -31,6 +31,45 @@ public class EntitiesTest { assertEquals(text, Entities.unescape(escapedUtfMin)); } + @Test public void escapedSupplemtary() { + String text = "\uD835\uDD59"; + String escapedAscii = Entities.escape(text, new OutputSettings().charset("ascii").escapeMode(base)); + assertEquals("𝕙", escapedAscii); + String escapedAsciiFull = Entities.escape(text, new OutputSettings().charset("ascii").escapeMode(extended)); + assertEquals("𝕙", escapedAsciiFull); + String escapedUtf= Entities.escape(text, new OutputSettings().charset("UTF-8").escapeMode(extended)); + assertEquals(text, escapedUtf); + } + + @Test public void unescapeMultiChars() { + String text = "≫ ⋙̸ ≫⃒ ≫̸ ≫ ≫"; // gg is not combo, but 8811 could conflict with NestedGreaterGreater or others + String un = "≫ ⋙̸ ≫⃒ ≫̸ ≫ ≫"; + assertEquals(un, Entities.unescape(text)); + String escaped = Entities.escape(un, new OutputSettings().charset("ascii").escapeMode(extended)); + assertEquals("≫ ⋙̸ ≫⃒ ≫̸ ≫ ≫", escaped); + assertEquals(un, Entities.unescape(escaped)); + } + + @Test public void xhtml() { + String text = "& > < ""; + assertEquals(38, xhtml.codepointForName("amp")); + assertEquals(62, xhtml.codepointForName("gt")); + assertEquals(60, xhtml.codepointForName("lt")); + assertEquals(34, xhtml.codepointForName("quot")); + + assertEquals("amp", xhtml.nameForCodepoint(38)); + assertEquals("gt", xhtml.nameForCodepoint(62)); + assertEquals("lt", xhtml.nameForCodepoint(60)); + assertEquals("quot", xhtml.nameForCodepoint(34)); + } + + @Test public void getByName() { + assertEquals("≫⃒", Entities.getByName("nGt")); + assertEquals("fj", Entities.getByName("fjlig")); + assertEquals("≫", Entities.getByName("gg")); + assertEquals("©", Entities.getByName("copy")); + } + @Test public void escapeSupplementaryCharacter() { String text = new String(Character.toChars(135361)); String escapedAscii = Entities.escape(text, new OutputSettings().charset("ascii").escapeMode(base)); @@ -39,9 +78,21 @@ public class EntitiesTest { assertEquals(text, escapedUtf); } + @Test public void notMissingMultis() { + String text = "⫽⃥"; + String un = "\u2AFD\u20E5"; + assertEquals(un, Entities.unescape(text)); + } + + @Test public void notMissingSupplementals() { + String text = "⨔ 𝔮"; + String un = "⨔ \uD835\uDD2E"; // 𝔮 + assertEquals(un, Entities.unescape(text)); + } + @Test public void unescape() { - String text = "Hello &<> ® Å &angst π π 新 there &! ¾ © ©"; - assertEquals("Hello &<> ® Å &angst π π 新 there &! ¾ © ©", Entities.unescape(text)); + String text = "Hello Æ &<> ® Å &angst π π 新 there &! ¾ © ©"; + assertEquals("Hello Æ &<> ® Å &angst π π 新 there &! ¾ © ©", Entities.unescape(text)); assertEquals("&0987654321; &unknown", Entities.unescape("&0987654321; &unknown")); } diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 79d516972a..70bcfa696f 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -210,7 +210,7 @@ public class CleanerTest { String defaultOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed()); assertNotSame(defaultOut, customOut); - assertEquals("

", customOut); + assertEquals("

", customOut); // entities now prefers shorted names if aliased assertEquals("
\n" + "

\n" + "
", defaultOut); From f0f0e41e6c581de43dfaa98f5c2af52e43e42e62 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 19 Aug 2016 12:54:51 -0700 Subject: [PATCH 035/774] Merge ahielg:master for attribute fix Fixes #746 --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/TokeniserState.java | 1 + src/test/java/org/jsoup/parser/AttributeParseTest.java | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 2818690ee9..15db6fc73c 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,9 @@ jsoup changelog * Fixed an OOB exception when loading an empty-body URL and parsing with the XML parser. + * Fixed an issue where attribute names starting with a slash would be parsed incorrectly. + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 3a2ac1f9e4..2e998d5219 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -880,6 +880,7 @@ void read(Tokeniser t, CharacterReader r) { break; default: t.error(this); + r.unconsume(); t.transition(BeforeAttributeName); } } diff --git a/src/test/java/org/jsoup/parser/AttributeParseTest.java b/src/test/java/org/jsoup/parser/AttributeParseTest.java index 3752ef746e..956220f970 100644 --- a/src/test/java/org/jsoup/parser/AttributeParseTest.java +++ b/src/test/java/org/jsoup/parser/AttributeParseTest.java @@ -6,6 +6,7 @@ import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.BooleanAttribute; +import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.junit.Test; @@ -90,4 +91,13 @@ public class AttributeParseTest { assertEquals(html, el.outerHtml()); } + @Test public void dropsSlashFromAttributeName() { + String html = ""; + Document doc = Jsoup.parse(html); + assertTrue("SelfClosingStartTag ignores last character", doc.select("img[onerror]").size() != 0); + assertEquals("", doc.body().html()); + + doc = Jsoup.parse(html, "", Parser.xmlParser()); + assertEquals("", doc.html()); + } } From 222feb1791388d1e94e2c99bb5858cf160904d16 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 19 Aug 2016 15:52:05 -0700 Subject: [PATCH 036/774] Include license and other files in jar/meta-inf Fixes #749 --- pom.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 445788c805..baec43861a 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ org.apache.maven.plugins maven-resources-plugin - 2.4 + 2.7 maven-release-plugin @@ -144,6 +144,16 @@ **/*.properties + + ./ + META-INF/ + false + + LICENSE + README + CHANGES + + From 3b2440a257df45fc49884a5c67bb0c9ce631781d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 19 Aug 2016 15:58:18 -0700 Subject: [PATCH 037/774] Don't reuse encoders, to make threadsafe Fixes #740 (well, I hope. I wasn't able to replicate it.) --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Document.java | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 15db6fc73c..12104a804b 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,9 @@ jsoup changelog * Fixed an issue where attribute names starting with a slash would be parsed incorrectly. + * Don't reuse charset encoders from OutputSettings, to make threadsafe. + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index c558c4c65f..b87fc18cc5 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -371,7 +371,6 @@ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset = Charset.forName("UTF-8"); - private CharsetEncoder charsetEncoder = charset.newEncoder(); private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; @@ -421,7 +420,6 @@ public Charset charset() { */ public OutputSettings charset(Charset charset) { this.charset = charset; - charsetEncoder = charset.newEncoder(); return this; } @@ -436,7 +434,7 @@ public OutputSettings charset(String charset) { } CharsetEncoder encoder() { - return charsetEncoder; + return charset.newEncoder(); } /** From d3c582598890d1e5e31e4f26e8452ad2dbf40b47 Mon Sep 17 00:00:00 2001 From: benoit Date: Tue, 23 Aug 2016 17:00:13 +0200 Subject: [PATCH 038/774] optimize Element#hasClass this method is perf sensitive (CPU and memory allocations) This replace the regexp split with a custom implementation that do not allocate unneeded memory. With this patch a simple test calling Element#select in a loop for several selector went from 3Gb allocated to 10Mb. GC times were reduced. Fixes #752 --- src/main/java/org/jsoup/nodes/Element.java | 46 ++++++++++++++-- .../java/org/jsoup/nodes/ElementTest.java | 55 +++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 205e4101b4..c3ac937040 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1067,15 +1067,49 @@ public Element classNames(Set classNames) { */ public boolean hasClass(String className) { String classAttr = attributes.get("class"); - if (classAttr.equals("") || classAttr.length() < className.length()) - return false; + final int end = classAttr.length(); + final int classNameLength = className.length(); - final String[] classes = classSplit.split(classAttr); - for (String name : classes) { - if (className.equalsIgnoreCase(name)) - return true; + // class attribute is empty or the requested class name is 'too' long + if (end == 0 || end < classNameLength) { + return false; + } + + // if both length are equals, just compare the className with the attribute + if(end == classNameLength) { + return className.equalsIgnoreCase(classAttr); } + // manually split the different class names in the class attibute + // DO NOT allocate the string but use regionMatches and length comparaison to make the check + boolean inClass = false; + int start = 0; + for (int i = 0; i < end; i ++) { + if (Character.isWhitespace(classAttr.charAt(i))) { + if(inClass) { + // the white space ends a class name + // compare it with the requested one + if(i-start == classNameLength && classAttr.regionMatches(true, start, className, 0, classNameLength)) { + return true; + } + inClass = false; + } + } + else { + if(!inClass) { + // we're in a class name : keep the start of the substring + inClass = true; + start = i; + } + } + } + + // the attribute may not end by a white space + // check the current class name + if(inClass && end-start == classNameLength) { + return classAttr.regionMatches(true, start, className, 0, classNameLength); + } + return false; } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 2afb5f33c8..e4e618215c 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -214,7 +214,62 @@ public class ElementTest { assertEquals(0, classes.size()); assertFalse(doc.hasClass("mellow")); } + + @Test public void testHasClassDomMethods() { + Tag tag = Tag.valueOf("a"); + Attributes attribs = new Attributes(); + Element el = new Element(tag, "", attribs); + + attribs.put("class", "toto"); + boolean hasClass = el.hasClass("toto"); + assertTrue(hasClass); + + attribs.put("class", " toto"); + hasClass = el.hasClass("toto"); + assertTrue(hasClass); + + attribs.put("class", "toto "); + hasClass = el.hasClass("toto"); + assertTrue(hasClass); + + attribs.put("class", "\ttoto "); + hasClass = el.hasClass("toto"); + assertTrue(hasClass); + + attribs.put("class", " toto "); + hasClass = el.hasClass("toto"); + assertTrue(hasClass); + + attribs.put("class", "ab"); + hasClass = el.hasClass("toto"); + assertFalse(hasClass); + + attribs.put("class", " "); + hasClass = el.hasClass("toto"); + assertFalse(hasClass); + + attribs.put("class", "tototo"); + hasClass = el.hasClass("toto"); + assertFalse(hasClass); + + attribs.put("class", "raulpismuth "); + hasClass = el.hasClass("raulpismuth"); + assertTrue(hasClass); + + attribs.put("class", " abcd raulpismuth efgh "); + hasClass = el.hasClass("raulpismuth"); + assertTrue(hasClass); + + attribs.put("class", " abcd efgh raulpismuth"); + hasClass = el.hasClass("raulpismuth"); + assertTrue(hasClass); + + attribs.put("class", " abcd efgh raulpismuth "); + hasClass = el.hasClass("raulpismuth"); + assertTrue(hasClass); + } + @Test public void testClassUpdates() { Document doc = Jsoup.parse("
"); Element div = doc.select("div").first(); From f41efcd0277bd426f1c409f2fe23080926050abf Mon Sep 17 00:00:00 2001 From: benoit Date: Tue, 23 Aug 2016 21:05:00 +0200 Subject: [PATCH 039/774] Optimize Attribute#iterator It allocates a lot of memory during the parsing through ParseSettings#normalizeAttributes. this new implementation just wrap the LinkedHashMap values iterator in a read-only iterator. Iterating 1000 times over htmltests/yahoo-article-1.html was allocating 970Mb, with this optimization it's now 350Mb Fixes #754 --- src/main/java/org/jsoup/nodes/Attributes.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index cc21fb6ef3..0959c44014 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -172,7 +172,11 @@ public void addAll(Attributes incoming) { } public Iterator iterator() { - return asList().iterator(); + if (attributes == null || attributes.isEmpty()) { + return Collections.emptyList().iterator(); + } + + return new AttributesReadOnlyIterator(attributes.values().iterator()); } /** @@ -334,4 +338,26 @@ public void remove() { private static String dataKey(String key) { return dataPrefix + key; } + + private static class AttributesReadOnlyIterator implements Iterator { + + private Iterator iterator; + + public AttributesReadOnlyIterator(Iterator iterator) { + this.iterator = iterator; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Attribute next() { + return iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + } } From b19d66e92d38de4f81428b5e26532ac3902f0acc Mon Sep 17 00:00:00 2001 From: benoit Date: Wed, 24 Aug 2016 10:30:04 +0200 Subject: [PATCH 040/774] add new tests for the new implementation of Attributes#iterator --- .../java/org/jsoup/nodes/AttributesTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 6f76e9298e..1af21260db 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -2,6 +2,8 @@ import static org.junit.Assert.*; +import java.util.Iterator; + import org.junit.Test; /** @@ -10,6 +12,7 @@ * @author Jonathan Hedley */ public class AttributesTest { + @Test public void html() { Attributes a = new Attributes(); a.put("Tot", "a&p"); @@ -33,5 +36,46 @@ public class AttributesTest { assertEquals(" Tot=\"a&p\" Hello=\"There\" data-name=\"Jsoup\"", a.html()); assertEquals(a.html(), a.toString()); } + + @Test(expected=UnsupportedOperationException.class) + public void testIteratorReadOnly() { + Attributes a = new Attributes(); + a.put("Tot", "a&p"); + a.put("Hello", "There"); + a.put("data-name", "Jsoup"); + + Iterator iterator = a.iterator(); + iterator.remove(); + } + + @Test + public void testIterator() { + Attributes a = new Attributes(); + String[][] datas = {{"Tot", "raul"}, + {"Hello", "pismuth"}, + {"data-name", "Jsoup"}}; + for (String[] atts : datas) { + a.put(atts[0], atts[1]); + } + + Iterator iterator = a.iterator(); + assertTrue(iterator.hasNext()); + int i = 0; + for (Attribute attribute : a) { + assertEquals(datas[i][0], attribute.getKey()); + assertEquals(datas[i][1], attribute.getValue()); + i++; + } + assertEquals(datas.length, i); + } + + @Test + public void testIteratorEmpty() { + Attributes a = new Attributes(); + + + Iterator iterator = a.iterator(); + assertFalse(iterator.hasNext()); + } } From 4f5a56459a98e21780d813919da66de9eee91b74 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Wed, 24 Aug 2016 17:04:45 -0700 Subject: [PATCH 041/774] Changelog for #753 and tidied up comments a little Fixes #753 --- CHANGES | 3 ++ src/main/java/org/jsoup/nodes/Element.java | 52 +++++++++------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/CHANGES b/CHANGES index 12104a804b..4e0cd2399e 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,9 @@ jsoup changelog is not defined by the server, or is defined incorrectly. + * Improved performance of class selectors by reducing memory allocation and garbase collection. + + * Fixed an issue when converting to the W3CDom XML, where valid (but ugly) HTML attribute names containing characters like '"' could not be converted into valid XML attribute names. These attribute names are now normalized if possible, or not added to the XML DOM. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index c3ac937040..fbbd160ee1 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1057,59 +1057,47 @@ public Element classNames(Set classNames) { * @param className name of class to check for * @return true if it does, false if not */ - /* - Used by common .class selector, so perf tweaked to reduce object creation vs hitting classnames(). - - Wiki: 71, 13 (5.4x) - CNN: 227, 91 (2.5x) - Alterslash: 59, 4 (14.8x) - Jsoup: 14, 1 (14x) - */ + // performance sensitive public boolean hasClass(String className) { - String classAttr = attributes.get("class"); - final int end = classAttr.length(); - final int classNameLength = className.length(); + final String classAttr = attributes.get("class"); + final int len = classAttr.length(); + final int wantLen = className.length(); - // class attribute is empty or the requested class name is 'too' long - if (end == 0 || end < classNameLength) { + if (len == 0 || len < wantLen) { return false; } - - // if both length are equals, just compare the className with the attribute - if(end == classNameLength) { + + // if both lengths are equal, only need compare the className with the attribute + if (len == wantLen) { return className.equalsIgnoreCase(classAttr); } - // manually split the different class names in the class attibute - // DO NOT allocate the string but use regionMatches and length comparaison to make the check + // otherwise, scan for whitespace and compare regions (with no string or arraylist allocations) boolean inClass = false; int start = 0; - for (int i = 0; i < end; i ++) { + for (int i = 0; i < len; i++) { if (Character.isWhitespace(classAttr.charAt(i))) { - if(inClass) { - // the white space ends a class name - // compare it with the requested one - if(i-start == classNameLength && classAttr.regionMatches(true, start, className, 0, classNameLength)) { + if (inClass) { + // white space ends a class name, compare it with the requested one, ignore case + if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) { return true; } inClass = false; } - } - else { - if(!inClass) { + } else { + if (!inClass) { // we're in a class name : keep the start of the substring inClass = true; start = i; } } } - - // the attribute may not end by a white space - // check the current class name - if(inClass && end-start == classNameLength) { - return classAttr.regionMatches(true, start, className, 0, classNameLength); + + // check the last entry + if (inClass && len - start == wantLen) { + return classAttr.regionMatches(true, start, className, 0, wantLen); } - + return false; } From 84ffc4b73ca48ebe2423eac7a5f7b0aaa6c93aad Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 25 Aug 2016 14:37:41 -0700 Subject: [PATCH 042/774] Don't override request content-type if set Fixes #756 --- CHANGES | 3 ++ .../java/org/jsoup/helper/HttpConnection.java | 6 +++- .../org/jsoup/integration/UrlConnectTest.java | 31 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 4e0cd2399e..21969758d5 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,9 @@ jsoup changelog * Don't reuse charset encoders from OutputSettings, to make threadsafe. + * Fixed an issue in connections with a requestBody where a custom content-type header could be ignored. + + *** Release 1.9.2 [2016-May-17] * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character would cause the parser to get stuck in an infinite loop. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 33f755acad..baf112cb57 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -855,7 +855,11 @@ else if (values.size() > 1) { private static String setOutputContentType(final Connection.Request req) { String bound = null; - if (needsMultipart(req)) { + if (req.hasHeader(CONTENT_TYPE)) { + // no-op; don't add content type as already set (e.g. for requestBody()) + // todo - if content type already set, we could add charset or boundary if those aren't included + } + else if (needsMultipart(req)) { bound = DataUtil.mimeBoundary(); req.header(CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bound); } else { diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index d212950f58..731b488b7d 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -113,6 +113,34 @@ public void doesPost() throws IOException { assertEquals("Jsoup, Jonathan", ihVal("uname", doc)); } + @Test + public void sendsRequestBodyJsonWithData() throws IOException { + final String body = "{key:value}"; + Document doc = Jsoup.connect(echoURL) + .requestBody(body) + .header("Content-Type", "application/json") + .userAgent(browserUa) + .data("foo", "true") + .post(); + assertEquals("POST", ihVal("REQUEST_METHOD", doc)); + assertEquals("application/json", ihVal("CONTENT_TYPE", doc)); + assertEquals("foo=true", ihVal("QUERY_STRING", doc)); + assertEquals(body, doc.select("th:contains(POSTDATA) ~ td").text()); + } + + @Test + public void sendsRequestBodyJsonWithoutData() throws IOException { + final String body = "{key:value}"; + Document doc = Jsoup.connect(echoURL) + .requestBody(body) + .header("Content-Type", "application/json") + .userAgent(browserUa) + .post(); + assertEquals("POST", ihVal("REQUEST_METHOD", doc)); + assertEquals("application/json", ihVal("CONTENT_TYPE", doc)); + assertEquals(body, doc.select("th:contains(POSTDATA) ~ td").text()); + } + @Test public void sendsRequestBody() throws IOException { final String body = "{key:value}"; @@ -122,7 +150,8 @@ public void sendsRequestBody() throws IOException { .userAgent(browserUa) .post(); assertEquals("POST", ihVal("REQUEST_METHOD", doc)); - assertEquals(body, ihVal("keywords", doc)); + assertEquals("text/plain", ihVal("CONTENT_TYPE", doc)); + assertEquals(body, doc.select("th:contains(POSTDATA) ~ td").text()); } @Test From a79d5e287a40634f0e41ee8371a742f57245d4bd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 25 Aug 2016 15:05:39 -0700 Subject: [PATCH 043/774] Changelog and tweak for #755 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Attributes.java | 26 ++----------------- .../java/org/jsoup/nodes/AttributesTest.java | 8 +++--- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/CHANGES b/CHANGES index 21969758d5..9d18df8504 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,9 @@ jsoup changelog * Improved performance of class selectors by reducing memory allocation and garbase collection. + * Improved performance of HTML output by reducing the creation of temporary attribute list iterators. + + * Fixed an issue when converting to the W3CDom XML, where valid (but ugly) HTML attribute names containing characters like '"' could not be converted into valid XML attribute names. These attribute names are now normalized if possible, or not added to the XML DOM. diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 0959c44014..30dffaff2d 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -175,8 +175,8 @@ public Iterator iterator() { if (attributes == null || attributes.isEmpty()) { return Collections.emptyList().iterator(); } - - return new AttributesReadOnlyIterator(attributes.values().iterator()); + + return attributes.values().iterator(); } /** @@ -338,26 +338,4 @@ public void remove() { private static String dataKey(String key) { return dataPrefix + key; } - - private static class AttributesReadOnlyIterator implements Iterator { - - private Iterator iterator; - - public AttributesReadOnlyIterator(Iterator iterator) { - this.iterator = iterator; - } - - public boolean hasNext() { - return iterator.hasNext(); - } - - public Attribute next() { - return iterator.next(); - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - } } diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 1af21260db..b4641234bb 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -36,16 +36,18 @@ public class AttributesTest { assertEquals(" Tot=\"a&p\" Hello=\"There\" data-name=\"Jsoup\"", a.html()); assertEquals(a.html(), a.toString()); } - - @Test(expected=UnsupportedOperationException.class) - public void testIteratorReadOnly() { + + @Test + public void testIteratorRemovable() { Attributes a = new Attributes(); a.put("Tot", "a&p"); a.put("Hello", "There"); a.put("data-name", "Jsoup"); Iterator iterator = a.iterator(); + iterator.next(); iterator.remove(); + assertEquals(2, a.size()); } @Test From 5f21bf317a983af1b363d90516d7321ca466e606 Mon Sep 17 00:00:00 2001 From: offa Date: Thu, 20 Oct 2016 11:45:40 +0200 Subject: [PATCH 044/774] Maven plugins updated to latest versions. --- pom.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index baec43861a..5b6cb03217 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ The MIT License https://jsoup.org/license repo - + https://github.com/jhy/jsoup @@ -36,7 +36,7 @@ org.apache.maven.plugins maven-compiler-plugin - 2.0.2 + 3.5.1 1.5 1.5 @@ -47,7 +47,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.9 + 1.15 animal-sniffer @@ -68,7 +68,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.6.1 + 2.10.4 -Xdoclint:none @@ -85,7 +85,7 @@ org.apache.maven.plugins maven-source-plugin - 2.1.1 + 3.0.1 @@ -101,7 +101,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.2 + 3.0.2 ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -111,7 +111,7 @@ org.apache.felix maven-bundle-plugin - 2.1.0 + 3.2.0 bundle-manifest @@ -130,11 +130,11 @@ org.apache.maven.plugins maven-resources-plugin - 2.7 + 3.0.1 maven-release-plugin - 2.5.2 + 2.5.3 @@ -198,7 +198,7 @@ - + @@ -218,7 +218,7 @@ - + From a1f8d852e06fb20f139d04914e2364bb55aee863 Mon Sep 17 00:00:00 2001 From: offa Date: Thu, 20 Oct 2016 17:17:10 +0200 Subject: [PATCH 045/774] CI: Build on multiple JDK's (OpenJDK and Oracle). --- .travis.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8a5369ae0..38d05239d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,11 @@ language: java + +jdk: + - openjdk6 + - openjdk7 + - oraclejdk7 + - oraclejdk8 + cache: - directories: - - $HOME/.m2 + directories: + - $HOME/.m2 From 7f8010df0ca535e9edf02985f480fded267df47b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Oct 2016 11:19:24 -0700 Subject: [PATCH 046/774] [maven-release-plugin] prepare release jsoup-1.10.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5b6cb03217..917a995e06 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.9.3-SNAPSHOT + 1.10.1 jsoup HTML parser https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.10.1 Jonathan Hedley From 8e01dfda30004fdf01d253ff947c29e6d7bf5d9e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Oct 2016 11:19:29 -0700 Subject: [PATCH 047/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 917a995e06..306cb30354 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.10.1 + 1.10.2-SNAPSHOT jsoup HTML parser https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.10.1 + HEAD Jonathan Hedley From d4ba661797050c3a84278bf8a0803269ab0f42f5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Oct 2016 11:45:44 -0700 Subject: [PATCH 048/774] Changelog release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9d18df8504..077cc06daa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.10.1 [PENDING] +*** Release 1.10.1 [2016-Oct-23] * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve case, according to the relevant spec. The CSS selectors for tags and attributes remain case insensitive, per the CSS From 7a04d72cc7600849ec98469a594a81fc89910be9 Mon Sep 17 00:00:00 2001 From: offa Date: Sun, 23 Oct 2016 21:23:07 +0200 Subject: [PATCH 049/774] Workaround for #772: Downgrade maven-bundle-plugin to v2.x. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 306cb30354..def76742e0 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ org.apache.felix maven-bundle-plugin - 3.2.0 + 2.5.4 bundle-manifest From 6dc38f26dd31f1a367961f27c56b143cd0462e5a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Oct 2016 17:59:25 -0700 Subject: [PATCH 050/774] Use jsoup's CharacterReader instead of regexes to parse Entities Faster, less memory. --- CHANGES | 4 + src/main/java/org/jsoup/helper/DataUtil.java | 9 +- src/main/java/org/jsoup/nodes/Entities.java | 87 ++++++++++++------- .../org/jsoup/parser/CharacterReader.java | 43 +++++++-- 4 files changed, 97 insertions(+), 46 deletions(-) diff --git a/CHANGES b/CHANGES index 077cc06daa..79389eeefd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ jsoup changelog +*** Release 1.10.2 [PENDING] + * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading + the HTML entity files. About 1.72x faster in this area. + *** Release 1.10.1 [2016-Oct-23] * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 0f44f9f000..b8bbefc7ae 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -26,8 +26,7 @@ public final class DataUtil { private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:\"|')?([^\\s,;\"']*)"); static final String defaultCharset = "UTF-8"; // used if not found in header or meta charset - private static final int bufferSize = 0x20000; // ~130K. - private static final int UNICODE_BOM = 0xFEFF; + private static final int bufferSize = 60000; private static final char[] mimeBoundaryChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); static final int boundaryLength = 32; @@ -146,11 +145,11 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba * @return the filled byte buffer * @throws IOException if an exception occurs whilst reading from the input stream. */ - static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) throws IOException { + public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) throws IOException { Validate.isTrue(maxSize >= 0, "maxSize must be 0 (unlimited) or larger"); final boolean capped = maxSize > 0; - byte[] buffer = new byte[bufferSize]; - ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize); + byte[] buffer = new byte[capped && maxSize < bufferSize ? maxSize : bufferSize]; + ByteArrayOutputStream outStream = new ByteArrayOutputStream(capped ? maxSize : bufferSize); int read; int remaining = maxSize; diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 1a9144fef0..fbb2ca1ef9 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -1,18 +1,18 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; +import org.jsoup.helper.DataUtil; import org.jsoup.helper.StringUtil; +import org.jsoup.parser.CharacterReader; import org.jsoup.parser.Parser; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.Arrays; import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.jsoup.nodes.Entities.EscapeMode.base; import static org.jsoup.nodes.Entities.EscapeMode.extended; @@ -23,17 +23,22 @@ * named character references. */ public class Entities { - private static Pattern entityPattern = Pattern.compile("^(\\w+)=(\\w+)(?:,(\\w+))?;(\\w+)$"); - static final int empty = -1; - static final String emptyName = ""; + private static final int empty = -1; + private static final String emptyName = ""; static final int codepointRadix = 36; public enum EscapeMode { - /** Restricted entities suitable for XHTML output: lt, gt, amp, and quot only. */ + /** + * Restricted entities suitable for XHTML output: lt, gt, amp, and quot only. + */ xhtml("entities-xhtml.properties", 4), - /** Default HTML output entities. */ + /** + * Default HTML output entities. + */ base("entities-base.properties", 106), - /** Complete HTML entities. */ + /** + * Complete HTML entities. + */ extended("entities-full.properties", 2125); // table of named references to their codepoints. sorted so we can binary search. built by BuildEntities. @@ -58,8 +63,8 @@ String nameForCodepoint(final int codepoint) { if (index >= 0) { // the results are ordered so lower case versions of same codepoint come after uppercase, and we prefer to emit lower // (and binary search for same item with multi results is undefined - return (index < nameVals.length-1 && codeKeys[index+1] == codepoint) ? - nameVals[index+1] : nameVals[index]; + return (index < nameVals.length - 1 && codeKeys[index + 1] == codepoint) ? + nameVals[index + 1] : nameVals[index]; } return emptyName; } @@ -76,6 +81,7 @@ private Entities() { /** * Check if the input is a known named entity + * * @param name the possible entity name (e.g. "lt" or "amp") * @return true if a known named entity */ @@ -85,6 +91,7 @@ public static boolean isNamedEntity(final String name) { /** * Check if the input is a known named entity in the base entity set. + * * @param name the possible entity name (e.g. "lt" or "amp") * @return true if a known named entity in the base set * @see #isNamedEntity(String) @@ -95,6 +102,7 @@ public static boolean isBaseNamedEntity(final String name) { /** * Get the Character value of the named entity + * * @param name named entity (e.g. "lt" or "amp") * @return the Character value of the named entity (e.g. '{@literal <}' or '{@literal &}') * @deprecated does not support characters outside the BMP or multiple character names @@ -105,6 +113,7 @@ public static Character getCharacterByName(String name) { /** * Get the character(s) represented by the named entitiy + * * @param name entity (e.g. "lt" or "amp") * @return the string value of the character(s) represented by this entity, or "" if not defined */ @@ -233,6 +242,7 @@ static String unescape(String string) { /** * Unescape the input string. + * * @param string to un-HTML-escape * @param strict if "strict" (that is, requires trailing ';' char, otherwise that's optional) * @return unescaped string @@ -278,6 +288,8 @@ private static CoreCharset byName(String name) { } } + private static final char[] codeDelims = {',', ';'}; + private static void load(EscapeMode e, String file, int size) { e.nameKeys = new String[size]; e.codeVals = new int[size]; @@ -287,32 +299,43 @@ private static void load(EscapeMode e, String file, int size) { InputStream stream = Entities.class.getResourceAsStream(file); if (stream == null) throw new IllegalStateException("Could not read resource " + file + ". Make sure you copy resources for " + Entities.class.getCanonicalName()); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); - String entry; + int i = 0; try { - while ((entry = reader.readLine()) != null) { + ByteBuffer bytes = DataUtil.readToByteBuffer(stream, 0); + String contents = Charset.forName("ascii").decode(bytes).toString(); + CharacterReader reader = new CharacterReader(contents); + + while (!reader.isEmpty()) { // NotNestedLessLess=10913,824;1887 - final Matcher match = entityPattern.matcher(entry); - if (match.find()) { - final String name = match.group(1); - final int cp1 = Integer.parseInt(match.group(2), codepointRadix); - final int cp2 = match.group(3) != null ? Integer.parseInt(match.group(3), codepointRadix) : empty; - final int index = Integer.parseInt(match.group(4), codepointRadix); - - e.nameKeys[i] = name; - e.codeVals[i] = cp1; - e.codeKeys[index] = cp1; - e.nameVals[index] = name; - - if (cp2 != empty) { - multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); - } - i++; + + final String name = reader.consumeTo('='); + reader.advance(); + final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); + final char codeDelim = reader.current(); + reader.advance(); + final int cp2; + if (codeDelim == ',') { + cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); + reader.advance(); + } else { + cp2 = empty; } + final int index = Integer.parseInt(reader.consumeTo('\n'), codepointRadix); + reader.advance(); + + e.nameKeys[i] = name; + e.codeVals[i] = cp1; + e.codeKeys[index] = cp1; + e.nameVals[index] = name; + + if (cp2 != empty) { + multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); + } + i++; + } - reader.close(); } catch (IOException err) { throw new IllegalStateException("Error reading resource " + file); } diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index f6d5fcd1ab..7f18619278 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -6,9 +6,9 @@ import java.util.Locale; /** - CharacterReader consumes tokens off a string. To replace the old TokenQueue. + CharacterReader consumes tokens off a string. Used internally by jsoup. API subject to changes. */ -final class CharacterReader { +public final class CharacterReader { static final char EOF = (char) -1; private static final int maxCacheLen = 12; @@ -18,21 +18,33 @@ final class CharacterReader { private int mark = 0; private final String[] stringCache = new String[512]; // holds reused strings in this doc, to lessen garbage - CharacterReader(String input) { + public CharacterReader(String input) { Validate.notNull(input); this.input = input.toCharArray(); this.length = this.input.length; } - int pos() { + /** + * Gets the current cursor position in the content. + * @return current position + */ + public int pos() { return pos; } - boolean isEmpty() { + /** + * Tests if all the content has been read. + * @return true if nothing left to read. + */ + public boolean isEmpty() { return pos >= length; } - char current() { + /** + * Get the char at the current position. + * @return char + */ + public char current() { return pos >= length ? EOF : input[pos]; } @@ -46,7 +58,10 @@ void unconsume() { pos--; } - void advance() { + /** + * Moves the current position by one. + */ + public void advance() { pos++; } @@ -100,7 +115,12 @@ int nextIndexOf(CharSequence seq) { return -1; } - String consumeTo(char c) { + /** + * Reads characters up to the specific char. + * @param c the delimiter + * @return the chars read + */ + public String consumeTo(char c) { int offset = nextIndexOf(c); if (offset != -1) { String consumed = cacheString(pos, offset); @@ -122,7 +142,12 @@ String consumeTo(String seq) { } } - String consumeToAny(final char... chars) { + /** + * Read characters until the first of any delimiters is found. + * @param chars delimiters to scan for + * @return characters read up to the matched delimiter. + */ + public String consumeToAny(final char... chars) { final int start = pos; final int remaining = length; final char[] val = input; From fa929d4f4b1576f3f2c4020892b74bc3518575e7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Oct 2016 18:37:53 -0700 Subject: [PATCH 051/774] Updated POM description --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index def76742e0..0b44b417ba 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,12 @@ 4.0.0 - jsoup + jsoup Java HTML Parser org.jsoup jsoup 1.10.2-SNAPSHOT - jsoup HTML parser + jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 From c28e5bf53a9ce9e32ab84ce2e6eba87ec747d1a0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 24 Oct 2016 18:01:41 -0700 Subject: [PATCH 052/774] Fixed handling of public/system flag in doctypes Fixes #408 --- CHANGES | 3 ++ .../java/org/jsoup/nodes/DocumentType.java | 28 +++++++++++++++++- .../jsoup/parser/HtmlTreeBuilderState.java | 2 +- src/main/java/org/jsoup/parser/Token.java | 6 ++++ .../java/org/jsoup/parser/TokeniserState.java | 8 +++-- .../java/org/jsoup/parser/XmlTreeBuilder.java | 2 +- .../org/jsoup/nodes/DocumentTypeTest.java | 29 +++++++++++++++++++ 7 files changed, 73 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 79389eeefd..40ad8856b7 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ jsoup changelog * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. + * Bugfix - a "SYSTEM" flag in doctype tags would be incorrectly removed. + + *** Release 1.10.1 [2016-Oct-23] * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index 838ec6f720..4e7730b128 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -9,7 +9,10 @@ * A {@code } node. */ public class DocumentType extends Node { + public static final String PUBLIC_KEY = "PUBLIC"; + public static final String SYSTEM_KEY = "SYSTEM"; private static final String NAME = "name"; + private static final String PUB_SYS_KEY = "pubSysKey"; // PUBLIC or SYSTEM private static final String PUBLIC_ID = "publicId"; private static final String SYSTEM_ID = "systemId"; // todo: quirk mode from publicId and systemId @@ -26,6 +29,27 @@ public DocumentType(String name, String publicId, String systemId, String baseUr attr(NAME, name); attr(PUBLIC_ID, publicId); + if (has(PUBLIC_ID)) { + attr(PUB_SYS_KEY, PUBLIC_KEY); + } + attr(SYSTEM_ID, systemId); + } + + /** + * Create a new doctype element. + * @param name the doctype's name + * @param publicId the doctype's public ID + * @param systemId the doctype's system ID + * @param baseUri the doctype's base URI + */ + public DocumentType(String name, String pubSysKey, String publicId, String systemId, String baseUri) { + super(baseUri); + + attr(NAME, name); + if (pubSysKey != null) { + attr(PUB_SYS_KEY, pubSysKey); + } + attr(PUBLIC_ID, publicId); attr(SYSTEM_ID, systemId); } @@ -44,8 +68,10 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr } if (has(NAME)) accum.append(" ").append(attr(NAME)); + if (has(PUB_SYS_KEY)) + accum.append(" ").append(attr(PUB_SYS_KEY)); if (has(PUBLIC_ID)) - accum.append(" PUBLIC \"").append(attr(PUBLIC_ID)).append('"'); + accum.append(" \"").append(attr(PUBLIC_ID)).append('"'); if (has(SYSTEM_ID)) accum.append(" \"").append(attr(SYSTEM_ID)).append('"'); accum.append('>'); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 9a4081405a..c515462c77 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -20,7 +20,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { // todo: quirk state check on doctype ids Token.Doctype d = t.asDoctype(); DocumentType doctype = new DocumentType( - tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri()); + tb.settings.normalizeTag(d.getName()), d.getPubSysKey(), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri()); tb.getDocument().appendChild(doctype); if (d.isForceQuirks()) tb.getDocument().quirksMode(Document.QuirksMode.quirks); diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 3b110c9125..34baf19699 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -32,6 +32,7 @@ static void reset(StringBuilder sb) { static final class Doctype extends Token { final StringBuilder name = new StringBuilder(); + String pubSysKey = null; final StringBuilder publicIdentifier = new StringBuilder(); final StringBuilder systemIdentifier = new StringBuilder(); boolean forceQuirks = false; @@ -43,6 +44,7 @@ static final class Doctype extends Token { @Override Token reset() { reset(name); + pubSysKey = null; reset(publicIdentifier); reset(systemIdentifier); forceQuirks = false; @@ -53,6 +55,10 @@ String getName() { return name.toString(); } + String getPubSysKey() { + return pubSysKey; + } + String getPublicIdentifier() { return publicIdentifier.toString(); } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 2e998d5219..6a97238d49 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -1,5 +1,7 @@ package org.jsoup.parser; +import org.jsoup.nodes.DocumentType; + import java.util.Arrays; /** @@ -1189,9 +1191,11 @@ void read(Tokeniser t, CharacterReader r) { else if (r.matches('>')) { t.emitDoctypePending(); t.advanceTransition(Data); - } else if (r.matchConsumeIgnoreCase("PUBLIC")) { + } else if (r.matchConsumeIgnoreCase(DocumentType.PUBLIC_KEY)) { + t.doctypePending.pubSysKey = DocumentType.PUBLIC_KEY; t.transition(AfterDoctypePublicKeyword); - } else if (r.matchConsumeIgnoreCase("SYSTEM")) { + } else if (r.matchConsumeIgnoreCase(DocumentType.SYSTEM_KEY)) { + t.doctypePending.pubSysKey = DocumentType.SYSTEM_KEY; t.transition(AfterDoctypeSystemKeyword); } else { t.error(this); diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 2ac525fc59..cfcb1a34c6 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -97,7 +97,7 @@ void insert(Token.Character characterToken) { } void insert(Token.Doctype d) { - DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier(), baseUri); + DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPubSysKey(), d.getPublicIdentifier(), d.getSystemIdentifier(), baseUri); insertNode(doctypeNode); } diff --git a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java index 12e6268ece..38110ff787 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java @@ -1,5 +1,7 @@ package org.jsoup.nodes; +import org.jsoup.Jsoup; +import org.jsoup.parser.Parser; import org.junit.Test; import static org.junit.Assert.*; @@ -38,4 +40,31 @@ public void constructorValidationOkWithBlankPublicAndSystemIds() { DocumentType combo = new DocumentType("notHtml", "--public", "--system", ""); assertEquals("", combo.outerHtml()); } + + @Test public void testRoundTrip() { + String base = ""; + assertEquals("", htmlOutput(base)); + assertEquals(base, xmlOutput(base)); + + String publicDoc = ""; + assertEquals(publicDoc, htmlOutput(publicDoc)); + assertEquals(publicDoc, xmlOutput(publicDoc)); + + String systemDoc = ""; + assertEquals(systemDoc, htmlOutput(systemDoc)); + assertEquals(systemDoc, xmlOutput(systemDoc)); + + String legacyDoc = ""; + assertEquals(legacyDoc, htmlOutput(legacyDoc)); + assertEquals(legacyDoc, xmlOutput(legacyDoc)); + } + + private String htmlOutput(String in) { + DocumentType type = (DocumentType) Jsoup.parse(in).childNode(0); + return type.outerHtml(); + } + + private String xmlOutput(String in) { + return Jsoup.parse(in, "", Parser.xmlParser()).childNode(0).outerHtml(); + } } From 2c58e975ab00eb369ee3dfbba07a4a78a1ef9e19 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Wed, 26 Oct 2016 12:58:24 -0700 Subject: [PATCH 053/774] Remove attributes using iterator to avoid CMEs. Fixes #759 --- CHANGES | 4 +- src/main/java/org/jsoup/nodes/Attributes.java | 5 ++- .../java/org/jsoup/nodes/AttributesTest.java | 39 ++++++++++++++----- .../java/org/jsoup/nodes/ElementTest.java | 15 +++++++ 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 40ad8856b7..30a1e7ca52 100644 --- a/CHANGES +++ b/CHANGES @@ -4,9 +4,11 @@ jsoup changelog * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. - * Bugfix - a "SYSTEM" flag in doctype tags would be incorrectly removed. + * Bugfix: a "SYSTEM" flag in doctype tags would be incorrectly removed. + * Bugfix: removing attributes from an Element with removeAttr() would cause a ConcurrentModificationException. + *** Release 1.10.1 [2016-Oct-23] * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 30dffaff2d..8fdb654d51 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -119,9 +119,10 @@ public void removeIgnoreCase(String key) { Validate.notEmpty(key); if (attributes == null) return; - for (String attrKey : attributes.keySet()) { + for (Iterator it = attributes.keySet().iterator(); it.hasNext(); ) { + String attrKey = it.next(); if (attrKey.equalsIgnoreCase(key)) - attributes.remove(attrKey); + it.remove(); } } diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index b4641234bb..cde3713185 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -1,10 +1,12 @@ package org.jsoup.nodes; -import static org.junit.Assert.*; +import org.junit.Test; import java.util.Iterator; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Tests for Attributes. @@ -12,8 +14,9 @@ * @author Jonathan Hedley */ public class AttributesTest { - - @Test public void html() { + + @Test + public void html() { Attributes a = new Attributes(); a.put("Tot", "a&p"); a.put("Hello", "There"); @@ -49,17 +52,17 @@ public void testIteratorRemovable() { iterator.remove(); assertEquals(2, a.size()); } - + @Test public void testIterator() { Attributes a = new Attributes(); String[][] datas = {{"Tot", "raul"}, - {"Hello", "pismuth"}, - {"data-name", "Jsoup"}}; + {"Hello", "pismuth"}, + {"data-name", "Jsoup"}}; for (String[] atts : datas) { a.put(atts[0], atts[1]); } - + Iterator iterator = a.iterator(); assertTrue(iterator.hasNext()); int i = 0; @@ -70,14 +73,30 @@ public void testIterator() { } assertEquals(datas.length, i); } - + @Test public void testIteratorEmpty() { Attributes a = new Attributes(); - Iterator iterator = a.iterator(); assertFalse(iterator.hasNext()); } + @Test + public void removeCaseSensitive() { + Attributes a = new Attributes(); + a.put("Tot", "a&p"); + a.put("tot", "one"); + a.put("Hello", "There"); + a.put("hello", "There"); + a.put("data-name", "Jsoup"); + + assertEquals(5, a.size()); + a.remove("Tot"); + a.remove("Hello"); + assertEquals(3, a.size()); + assertTrue(a.hasKey("tot")); + assertFalse(a.hasKey("Tot")); + } + } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index e4e618215c..6eef9a96c6 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -957,4 +957,19 @@ public void testNamespacedElements() { assertEquals(1, els.size()); assertEquals("html > body > fb|comments", els.get(0).cssSelector()); } + + @Test + public void testChainedRemoveAttributes() { + String html = "Text"; + Document doc = Jsoup.parse(html); + Element a = doc.select("a").first(); + a + .removeAttr("zero") + .removeAttr("one") + .removeAttr("two") + .removeAttr("three") + .removeAttr("four") + .removeAttr("five"); + assertEquals("Text", a.outerHtml()); + } } From 4f707008353e272c12a7a6543d6ecfc666a7cc64 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 14:31:33 -0700 Subject: [PATCH 054/774] Implemented Element.is() --- CHANGES | 2 ++ src/main/java/org/jsoup/nodes/Element.java | 14 +++++++++++ src/main/java/org/jsoup/select/Elements.java | 8 +++++-- .../java/org/jsoup/select/QueryParser.java | 2 +- .../java/org/jsoup/nodes/ElementTest.java | 23 +++++++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 30a1e7ca52..056ca08fd2 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,8 @@ jsoup changelog * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. + * Added Element.is(query) to check if an element matches this CSS query. + * Bugfix: a "SYSTEM" flag in doctype tags would be incorrectly removed. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index fbbd160ee1..002faaaffb 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -10,6 +10,7 @@ import org.jsoup.select.Evaluator; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; +import org.jsoup.select.QueryParser; import org.jsoup.select.Selector; import java.io.IOException; @@ -286,6 +287,19 @@ public List dataNodes() { public Elements select(String cssQuery) { return Selector.select(cssQuery, this); } + + /** + * Check if this element matches the given {@link Selector} CSS query. + * @param cssQuery a {@link Selector} CSS query + * @return if this element matches the query + */ + public boolean is(String cssQuery) { + return is(QueryParser.parse(cssQuery)); + } + + public boolean is(Evaluator evaluator) { + return evaluator.matches(this.ownerDocument(), this); + } /** * Add a node child node to this element. diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 24f7c23435..be7cd1a2a6 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -440,8 +440,12 @@ public Elements eq(int index) { * @return true if at least one element in the list matches the query. */ public boolean is(String query) { - Elements children = select(query); - return !children.isEmpty(); + Evaluator eval = QueryParser.parse(query); + for (Element e : this) { + if (e.is(eval)) + return true; + } + return false; } /** diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index cf11de1d47..431468936d 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -12,7 +12,7 @@ /** * Parses a CSS selector into an Evaluator tree. */ -class QueryParser { +public class QueryParser { private final static String[] combinators = {",", ">", "+", "~", " "}; private static final String[] AttributeEvals = new String[]{"=", "!=", "^=", "$=", "*=", "~="}; diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 6eef9a96c6..48a719cd66 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -972,4 +972,27 @@ public void testChainedRemoveAttributes() { .removeAttr("five"); assertEquals("Text", a.outerHtml()); } + + @Test + public void testIs() { + String html = "

One Two Three

Another

"; + Document doc = Jsoup.parse(html); + Element p = doc.select("p").first(); + + assertTrue(p.is("p")); + assertFalse(p.is("div")); + assertTrue(p.is("p:has(a)")); + assertTrue(p.is("p:first-child")); + assertFalse(p.is("p:last-child")); + assertTrue(p.is("*")); + assertTrue(p.is("div p")); + + Element q = doc.select("p").last(); + assertTrue(q.is("p")); + assertTrue(q.is("p ~ p")); + assertTrue(q.is("p + p")); + assertTrue(q.is("p:last-child")); + assertFalse(q.is("p a")); + assertFalse(q.is("a")); + } } From c43e4875338e95bd88fe9fe0ddcc5ba2f4e9c418 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 14:43:14 -0700 Subject: [PATCH 055/774] Javadoc note --- src/main/java/org/jsoup/nodes/Element.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 002faaaffb..76370cd173 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -297,6 +297,11 @@ public boolean is(String cssQuery) { return is(QueryParser.parse(cssQuery)); } + /** + * Check if this element matches the given evaluator. + * @param evaluator an element evaluator + * @return if this element matches + */ public boolean is(Evaluator evaluator) { return evaluator.matches(this.ownerDocument(), this); } From fcd8807033d27e7591291cd5143d912dd94e09c3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 15:56:16 -0700 Subject: [PATCH 056/774] Support prev and next etc --- CHANGES | 3 + src/main/java/org/jsoup/select/Elements.java | 85 +++++++++++++++++++ .../java/org/jsoup/select/ElementsTest.java | 45 ++++++++++ 3 files changed, 133 insertions(+) diff --git a/CHANGES b/CHANGES index 056ca08fd2..542cf47d9c 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,9 @@ jsoup changelog * Added Element.is(query) to check if an element matches this CSS query. + * Added new methods to Elements: next(query), nextAll(query), prev(query), prevAll(query) to select next and previous + element siblings from a current selection, with optional selectors. + * Bugfix: a "SYSTEM" flag in doctype tags would be incorrectly removed. diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index be7cd1a2a6..e9e851d1de 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -448,6 +448,91 @@ public boolean is(String query) { return false; } + /** + * Get the immediate next element sibling of each element in this list. + * @return next element siblings. + */ + public Elements next() { + return siblings(null, true, false); + } + + /** + * Get the immediate next element sibling of each element in this list, filtered by the query. + * @param query CSS query to match siblings against + * @return next element siblings. + */ + public Elements next(String query) { + return siblings(query, true, false); + } + + /** + * Get all of the following element siblings of each element in this list. + * @return all following element siblings. + */ + public Elements nextAll() { + return siblings(null, true, true); + } + + /** + * Get all of the following element siblings of each element in this list, filtered by the query. + * @param query CSS query to match siblings against + * @return all following element siblings. + */ + public Elements nextAll(String query) { + return siblings(query, true, true); + } + + /** + * Get the immediate previous element sibling of each element in this list. + * @return previous element siblings. + */ + public Elements prev() { + return siblings(null, false, false); + } + + /** + * Get the immediate previous element sibling of each element in this list, filtered by the query. + * @param query CSS query to match siblings against + * @return previous element siblings. + */ + public Elements prev(String query) { + return siblings(query, false, false); + } + + /** + * Get all of the previous element siblings of each element in this list. + * @return all previous element siblings. + */ + public Elements prevAll() { + return siblings(null, false, true); + } + + /** + * Get all of the previous element siblings of each element in this list, filtered by the query. + * @param query CSS query to match siblings against + * @return all previous element siblings. + */ + public Elements prevAll(String query) { + return siblings(query, false, true); + } + + private Elements siblings(String query, boolean next, boolean all) { + Elements els = new Elements(); + Evaluator eval = query != null? QueryParser.parse(query) : null; + for (Element e : this) { + do { + Element sib = next ? e.nextElementSibling() : e.previousElementSibling(); + if (sib == null) break; + if (eval == null) + els.add(sib); + else if (sib.is(eval)) + els.add(sib); + e = sib; + } while (all); + } + return els; + } + /** * Get all of the parents and ancestor elements of the matched elements. * @return all of the parents and ancestor elements of the matched elements diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index f20df7f887..79ca967ef7 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -283,4 +283,49 @@ public void tail(Node node, int depth) { assertEquals(1, els.size()); assertEquals("Check", els.text()); } + + @Test public void siblings() { + Document doc = Jsoup.parse("

1

2

3

4

5

6

7

8

9

10

11

12

"); + + Elements els = doc.select("p:eq(3)"); // gets p4 and p10 + assertEquals(2, els.size()); + + Elements next = els.next(); + assertEquals(2, next.size()); + assertEquals("5", next.first().text()); + assertEquals("11", next.last().text()); + + assertEquals(0, els.next("p:contains(6)").size()); + final Elements nextF = els.next("p:contains(5)"); + assertEquals(1, nextF.size()); + assertEquals("5", nextF.first().text()); + + Elements nextA = els.nextAll(); + assertEquals(4, nextA.size()); + assertEquals("5", nextA.first().text()); + assertEquals("12", nextA.last().text()); + + Elements nextAF = els.nextAll("p:contains(6)"); + assertEquals(1, nextAF.size()); + assertEquals("6", nextAF.first().text()); + + Elements prev = els.prev(); + assertEquals(2, prev.size()); + assertEquals("3", prev.first().text()); + assertEquals("9", prev.last().text()); + + assertEquals(0, els.prev("p:contains(1)").size()); + final Elements prevF = els.prev("p:contains(3)"); + assertEquals(1, prevF.size()); + assertEquals("3", prevF.first().text()); + + Elements prevA = els.prevAll(); + assertEquals(6, prevA.size()); + assertEquals("3", prevA.first().text()); + assertEquals("7", prevA.last().text()); + + Elements prevAF = els.prevAll("p:contains(1)"); + assertEquals(1, prevAF.size()); + assertEquals("1", prevAF.first().text()); + } } From 97a270ce61059c876e9ff69261c08c64664bfe13 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 16:41:49 -0700 Subject: [PATCH 057/774] Don't recurse in ownerDocument --- src/main/java/org/jsoup/nodes/Node.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 124f73934c..a8ea2e8f43 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -239,7 +239,7 @@ public Node parent() { } /** - Gets this node's parent node. Node overridable by extending classes, so useful if you really just need the Node type. + Gets this node's parent node. Not overridable by extending classes, so useful if you really just need the Node type. @return parent node; or null if no parent. */ public final Node parentNode() { @@ -251,12 +251,15 @@ public final Node parentNode() { * @return the Document associated with this Node, or null if there is no such Document. */ public Document ownerDocument() { - if (this instanceof Document) - return (Document) this; - else if (parentNode == null) - return null; - else - return parentNode.ownerDocument(); + Node node = this; + while (true) { + if (node instanceof Document) + return (Document) node; + else if (node.parentNode == null) + return null; + else + node = node.parentNode; + } } /** From f78df946f00cdc9ed2159716b50a785b12b57aca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 17:25:56 -0700 Subject: [PATCH 058/774] Node.root() --- CHANGES | 2 ++ src/main/java/org/jsoup/nodes/Element.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 25 ++++++++++++--------- src/test/java/org/jsoup/nodes/NodeTest.java | 15 +++++++++++++ 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 542cf47d9c..1d6c08498c 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,8 @@ jsoup changelog * Added new methods to Elements: next(query), nextAll(query), prev(query), prevAll(query) to select next and previous element siblings from a current selection, with optional selectors. + * Added Node.root() to get the topmost ancestor of a Node. + * Bugfix: a "SYSTEM" flag in doctype tags would be incorrectly removed. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 76370cd173..c09f96a650 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -303,7 +303,7 @@ public boolean is(String cssQuery) { * @return if this element matches */ public boolean is(Evaluator evaluator) { - return evaluator.matches(this.ownerDocument(), this); + return evaluator.matches((Element)this.root(), this); } /** diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index a8ea2e8f43..ed19156538 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -245,21 +245,25 @@ public Node parent() { public final Node parentNode() { return parentNode; } + + /** + * Get this node's root node; that is, its topmost ancestor. If this node is the top ancestor, returns {@code this}. + * @return topmost ancestor. + */ + public Node root() { + Node node = this; + while (node.parentNode != null) + node = node.parentNode; + return node; + } /** * Gets the Document associated with this Node. * @return the Document associated with this Node, or null if there is no such Document. */ public Document ownerDocument() { - Node node = this; - while (true) { - if (node instanceof Document) - return (Document) node; - else if (node.parentNode == null) - return null; - else - node = node.parentNode; - } + Node root = root(); + return (root instanceof Document) ? (Document) root : null; } /** @@ -557,7 +561,8 @@ protected void outerHtml(Appendable accum) { // if this node has no document (or parent), retrieve the default output settings Document.OutputSettings getOutputSettings() { - return ownerDocument() != null ? ownerDocument().outputSettings() : (new Document("")).outputSettings(); + Document owner = ownerDocument(); + return owner != null ? owner.outputSettings() : (new Document("")).outputSettings(); } /** diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index a9274dbc15..7358c3888b 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -157,6 +157,21 @@ public void handlesAbsOnProtocolessAbsoluteUris() { assertNull(doc.parent()); } + @Test public void root() { + Document doc = Jsoup.parse("

Hello"); + Element p = doc.select("p").first(); + Node root = p.root(); + assertTrue(doc == root); + assertNull(root.parent()); + assertTrue(doc.root() == doc); + assertTrue(doc.root() == doc.ownerDocument()); + + Element standAlone = new Element(Tag.valueOf("p"), ""); + assertTrue(standAlone.parent() == null); + assertTrue(standAlone.root() == standAlone); + assertTrue(standAlone.ownerDocument() == null); + } + @Test public void before() { Document doc = Jsoup.parse("

One two three

"); Element newNode = new Element(Tag.valueOf("em"), ""); From 38d04b7dfc11c37e29548d8b046b9b209dfd1fd3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 17:32:03 -0700 Subject: [PATCH 059/774] Simple Element contstructor --- src/main/java/org/jsoup/nodes/Element.java | 8 ++++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index c09f96a650..e9e653d826 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -38,6 +38,14 @@ public class Element extends Node { private static final Pattern classSplit = Pattern.compile("\\s+"); + /** + * Create a new, standalone element. + * @param tag tag name + */ + public Element(String tag) { + this(Tag.valueOf(tag), "", new Attributes()); + } + /** * Create a new, standalone Element. (Standalone in that is has no parent.) * diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 48a719cd66..458afd9445 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -995,4 +995,10 @@ public void testIs() { assertFalse(q.is("p a")); assertFalse(q.is("a")); } + + + @Test public void elementByTagName() { + Element a = new Element("P"); + assertTrue(a.tagName().equals("P")); + } } From 2cad0592106c30b04fe328b3989af619dcb6a49c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 21:43:13 -0700 Subject: [PATCH 060/774] Handle UTF headers and encode URLs better Fixes #706 --- CHANGES | 6 ++ .../java/org/jsoup/helper/HttpConnection.java | 83 +++++++++++++++++-- .../org/jsoup/integration/UrlConnectTest.java | 38 +++++++++ 3 files changed, 122 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 1d6c08498c..63ba0dfc80 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,12 @@ jsoup changelog * Added Node.root() to get the topmost ancestor of a Node. + * In Jsoup.Connect, now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts + the header value appropriately. + + * Bugfix: in Jsoup.Connect, URLs containing non-URL-safe characters were not encoded to URL safe correctly. + + * Bugfix: a "SYSTEM" flag in doctype tags would be incorrectly removed. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index baf112cb57..f070ed6902 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -43,12 +43,30 @@ public static Connection connect(URL url) { return con; } + /** + * Encodes the input URL into a safe ASCII URL string + * @param url unescaped URL + * @return escaped URL + */ private static String encodeUrl(String url) { - if(url == null) - return null; - return url.replaceAll(" ", "%20"); + try { + URL u = new URL(url); + return encodeUrl(u).toExternalForm(); + } catch (Exception e) { + return url; + } } + private static URL encodeUrl(URL u) { + try { + // odd way to encode urls, but it works! + final URI uri = new URI(u.getProtocol(), u.getUserInfo(), u.getHost(), u.getPort(), u.getPath(), u.getQuery(), u.getRef()); + return new URL(uri.toASCIIString()); + } catch (Exception e) { + return u; + } + } + private static String encodeMimeName(String val) { if (val == null) return null; @@ -293,7 +311,61 @@ public T method(Method method) { public String header(String name) { Validate.notNull(name, "Header name must not be null"); - return getHeaderCaseInsensitive(name); + String val = getHeaderCaseInsensitive(name); + if (val != null) { + // headers should be ISO8859 - but values are often actually UTF-8. Test if it looks like UTF8 and convert if so + val = fixHeaderEncoding(val); + } + return val; + } + + private static String fixHeaderEncoding(String val) { + try { + byte[] bytes = val.getBytes("ISO-8859-1"); + if (!looksLikeUtf8(bytes)) + return val; + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // shouldn't happen as these both always exist + return val; + } + } + + private static boolean looksLikeUtf8(byte[] input) { + int i = 0; + // BOM: + if (input.length >= 3 && (input[0] & 0xFF) == 0xEF + && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) { + i = 3; + } + + int end; + for (int j = input.length; i < j; ++i) { + int o = input[i]; + if ((o & 0x80) == 0) { + continue; // ASCII + } + + // UTF-8 leading: + if ((o & 0xE0) == 0xC0) { + end = i + 1; + } else if ((o & 0xF0) == 0xE0) { + end = i + 2; + } else if ((o & 0xF8) == 0xF0) { + end = i + 3; + } else { + return false; + } + + while (i < end) { + i++; + o = input[i]; + if ((o & 0xC0) != 0x80) { + return false; + } + } + } + return true; } public T header(String name, String value) { @@ -587,7 +659,8 @@ else if (methodHasBody) String location = res.header(LOCATION); if (location != null && location.startsWith("http:/") && location.charAt(6) != '/') // fix broken Location: http:/temp/AAG_New/en/index.php location = location.substring(6); - req.url(StringUtil.resolve(req.url(), encodeUrl(location))); + URL redir = StringUtil.resolve(req.url(), location); + req.url(encodeUrl(redir)); for (Map.Entry cookie : res.cookies.entrySet()) { // add response cookies to request (for e.g. login posts) req.cookie(cookie.getKey(), cookie.getValue()); diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 731b488b7d..b4883fc70f 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -723,4 +723,42 @@ public void canSpecifyResponseCharset() throws IOException { assertEquals("Cost is €100", doc3.select("p").text()); assertTrue(res3.body().contains("€")); } + + @Test + public void handlesUnescapedRedirects() throws IOException { + // URL locations should be url safe (ascii) but are often not, so we should try to guess + // in this case the location header is utf-8, but defined in spec as iso8859, so detect, convert, encode + String url = "http://direct.infohound.net/tools/302-utf.pl"; + String urlEscaped = "http://direct.infohound.net/tools/test%F0%9F%92%A9.html"; + + Connection.Response res = Jsoup.connect(url).execute(); + Document doc = res.parse(); + assertEquals(doc.body().text(), "\uD83D\uDCA9!"); + assertEquals(doc.location(), urlEscaped); + + Connection.Response res2 = Jsoup.connect(url).followRedirects(false).execute(); + assertEquals("/tools/test\uD83D\uDCA9.html", res2.header("Location")); + // if we didn't notice it was utf8, would look like: Location: /tools/test💩.html + } + + @Test + public void handlesUt8fInUrl() throws IOException { + String url = "http://direct.infohound.net/tools/test\uD83D\uDCA9.html"; + String urlEscaped = "http://direct.infohound.net/tools/test%F0%9F%92%A9.html"; + + Connection.Response res = Jsoup.connect(url).execute(); + Document doc = res.parse(); + assertEquals("\uD83D\uDCA9!", doc.body().text()); + assertEquals(urlEscaped, doc.location()); + } + + @Test + public void inWildUtfRedirect() throws IOException { + Connection.Response res = Jsoup.connect("http://brabantn.ws/Q4F").execute(); + Document doc = res.parse(); + assertEquals( + "http://www.omroepbrabant.nl/?news/2474781303/Gestrande+ree+in+Oss+niet+verdoofd,+maar+doodgeschoten+%E2%80%98Dit+kan+gewoon+niet,+bizar%E2%80%99+[VIDEO].aspx", + doc.location() + ); + } } From 47f5fd02266460630239483cd164ab8f4bb85f28 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 21:47:26 -0700 Subject: [PATCH 061/774] Test for #549 --- .../java/org/jsoup/integration/UrlConnectTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index b4883fc70f..dc162cb63a 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -761,4 +761,14 @@ public void inWildUtfRedirect() throws IOException { doc.location() ); } + + @Test + public void inWildUtfRedirect2() throws IOException { + Connection.Response res = Jsoup.connect("https://ssl.souq.com/sa-en/2724288604627/s").execute(); + Document doc = res.parse(); + assertEquals( + "http://saudi.souq.com/sa-en/%D8%AE%D8%B2%D9%86%D8%A9-%D8%A2%D9%85%D9%86%D8%A9-3-%D8%B7%D8%A8%D9%82%D8%A7%D8%AA-%D8%A8%D9%86%D8%B8%D8%A7%D9%85-%D9%82%D9%81%D9%84-%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-bsd11523-6831477/i/?ctype=dsrch", + doc.location() + ); + } } From cd45173d0787948003ffc41bba0ffbd34c915900 Mon Sep 17 00:00:00 2001 From: Alon Cohen Date: Tue, 31 May 2016 17:24:39 +0300 Subject: [PATCH 062/774] Suggesting an implementation to issue #711 --- src/main/java/org/jsoup/helper/DataUtil.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index b8bbefc7ae..76139c4f1f 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -153,7 +153,7 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr int read; int remaining = maxSize; - while (true) { + while (!Thread.currentThread().isInterrupted()) { read = inStream.read(buffer); if (read == -1) break; if (capped) { @@ -165,6 +165,11 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr } outStream.write(buffer, 0, read); } + + if (Thread.currentThread().isInterrupted()) { + Thread.interrupted(); + throw new IOException("JSoup thread has been interrupted"); + } return ByteBuffer.wrap(outStream.toByteArray()); } From 032c97320f4ca7256a60e7319b6354c62971702c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Oct 2016 22:39:00 -0700 Subject: [PATCH 063/774] Tidied up interrupt check, added test case to make sure it works. --- CHANGES | 4 +++ src/main/java/org/jsoup/helper/DataUtil.java | 11 +++----- .../org/jsoup/integration/UrlConnectTest.java | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 63ba0dfc80..c2f5af9a70 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,10 @@ jsoup changelog * Added Node.root() to get the topmost ancestor of a Node. + * Allow the Jsoup.Connect thread to be interrupted when reading the input stream; helps when reading from a long stream + of data that doesn't read timeout. + + * In Jsoup.Connect, now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts the header value appropriately. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 76139c4f1f..b32d86af5d 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -139,7 +139,8 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba } /** - * Read the input stream into a byte buffer. + * Read the input stream into a byte buffer. To deal with slow input streams, you may interrupt the thread this + * method is executing on. The data read until being interrupted will be available. * @param inStream the input stream to read from * @param maxSize the maximum size in bytes to read from the stream. Set to 0 to be unlimited. * @return the filled byte buffer @@ -153,7 +154,7 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr int read; int remaining = maxSize; - while (!Thread.currentThread().isInterrupted()) { + while (!Thread.interrupted()) { read = inStream.read(buffer); if (read == -1) break; if (capped) { @@ -165,11 +166,7 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr } outStream.write(buffer, 0, read); } - - if (Thread.currentThread().isInterrupted()) { - Thread.interrupted(); - throw new IOException("JSoup thread has been interrupted"); - } + return ByteBuffer.wrap(outStream.toByteArray()); } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index dc162cb63a..041e34f5ac 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -771,4 +771,29 @@ public void inWildUtfRedirect2() throws IOException { doc.location() ); } + + @Test public void canInterruptRead() throws IOException, InterruptedException { + final String[] body = new String[1]; + Thread runner = new Thread(new Runnable() { + public void run() { + try { + Connection.Response res = Jsoup.connect("http://jsscxml.org/serverload.stream") + .timeout(10 * 1000) + .execute(); + body[0] = res.body(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + }); + + runner.start(); + Thread.sleep(1000 * 5); + runner.interrupt(); + assertTrue(runner.isInterrupted()); + runner.join(); + + assertTrue(body[0].length() > 0); + } } From 2a9f635f6448f101916e3b723af567149a94ab0b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 30 Oct 2016 12:37:40 -0700 Subject: [PATCH 064/774] Updated default timeout and user-agent --- CHANGES | 10 ++++++++-- src/main/java/org/jsoup/Connection.java | 3 ++- src/main/java/org/jsoup/helper/HttpConnection.java | 12 ++++++++++-- .../java/org/jsoup/helper/HttpConnectionTest.java | 4 +++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index c2f5af9a70..8d122ab3f3 100644 --- a/CHANGES +++ b/CHANGES @@ -15,8 +15,14 @@ jsoup changelog of data that doesn't read timeout. - * In Jsoup.Connect, now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts - the header value appropriately. + * Jsoup.Connect now uses a desktop user-agent by default. Many users were getting caught by not specifying a + user-agent, and so the default of 'Java' was used. That causes many servers to return different content than what + they would to a desktop browser, and what the developer was expecting. + + * Increased the default connect/read timeout in Jsoup.Connect to 30 seconds. + + * Jsoup.Connect now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts + the header value appropriately. This improves compatibility with servers that are configured incorrectly. * Bugfix: in Jsoup.Connect, URLs containing non-URL-safe characters were not encoded to URL safe correctly. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index e89cf4d4b2..c71189a7e7 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -78,12 +78,13 @@ public final boolean hasBody() { * Set the request user-agent header. * @param userAgent user-agent to use * @return this Connection, for chaining + * @see org.jsoup.helper.HttpConnection#DEFAULT_UA */ Connection userAgent(String userAgent); /** * Set the request timeouts (connect and read). If a timeout occurs, an IOException will be thrown. The default - * timeout is 3 seconds (3000 millis). A timeout of zero is treated as an infinite timeout. + * timeout is (30000 millis). A timeout of zero is treated as an infinite timeout. * @param millis number of milliseconds (thousandths of a second) before timing out connects or reads. * @return this Connection, for chaining */ diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index f070ed6902..87fd9aaf8e 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -26,6 +26,13 @@ */ public class HttpConnection implements Connection { public static final String CONTENT_ENCODING = "Content-Encoding"; + /** + * Many users would get caught by not setting a user-agent and therefore getting different responses on their desktop + * vs in jsoup, which would otherwise default to {@code Java}. So by default, use a desktop UA. + */ + public static final String DEFAULT_UA = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36"; + private static final String USER_AGENT = "User-Agent"; private static final String CONTENT_TYPE = "Content-Type"; private static final String MULTIPART_FORM_DATA = "multipart/form-data"; private static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; @@ -108,7 +115,7 @@ public Connection proxy(String host, int port) { public Connection userAgent(String userAgent) { Validate.notNull(userAgent, "User agent must not be null"); - req.header("User-Agent", userAgent); + req.header(USER_AGENT, userAgent); return this; } @@ -466,12 +473,13 @@ public static class Request extends HttpConnection.Base impl private String postDataCharset = DataUtil.defaultCharset; private Request() { - timeoutMilliseconds = 3000; + timeoutMilliseconds = 30000; // 30 seconds maxBodySizeBytes = 1024 * 1024; // 1MB followRedirects = true; data = new ArrayList(); method = Method.GET; headers.put("Accept-Encoding", "gzip"); + headers.put(USER_AGENT, DEFAULT_UA); parser = Parser.htmlParser(); } diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 842828af49..09e52e2ede 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -116,12 +116,14 @@ public class HttpConnectionTest { @Test public void userAgent() { Connection con = HttpConnection.connect("http://example.com/"); + assertEquals(HttpConnection.DEFAULT_UA, con.request().header("User-Agent")); con.userAgent("Mozilla"); assertEquals("Mozilla", con.request().header("User-Agent")); } @Test public void timeout() { Connection con = HttpConnection.connect("http://example.com/"); + assertEquals(30 * 1000, con.request().timeout()); con.timeout(1000); assertEquals(1000, con.request().timeout()); } @@ -139,7 +141,7 @@ public class HttpConnectionTest { assertEquals(Connection.Method.POST, con.request().method()); } - @Test(expected=IllegalArgumentException.class) public void throwsOnOdddData() { + @Test(expected=IllegalArgumentException.class) public void throwsOnOddData() { Connection con = HttpConnection.connect("http://example.com/"); con.data("Name", "val", "what"); } From 1038abfd34a21905e176e6d28f6377c90fce27d7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 30 Oct 2016 13:27:38 -0700 Subject: [PATCH 065/774] Added :containsData() selector --- CHANGES | 4 +++ src/main/java/org/jsoup/nodes/Element.java | 3 ++ src/main/java/org/jsoup/select/Evaluator.java | 29 ++++++++++++++++--- .../java/org/jsoup/select/QueryParser.java | 10 +++++++ src/main/java/org/jsoup/select/Selector.java | 1 + .../java/org/jsoup/select/SelectorTest.java | 22 ++++++++++++++ 6 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 8d122ab3f3..26b5fa6e17 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,8 @@ jsoup changelog * Added Node.root() to get the topmost ancestor of a Node. + * Added the new selector :containsData(), to find elements that hold data, like script and style tags. + * Allow the Jsoup.Connect thread to be interrupted when reading the input stream; helps when reading from a long stream of data that doesn't read timeout. @@ -32,6 +34,8 @@ jsoup changelog * Bugfix: removing attributes from an Element with removeAttr() would cause a ConcurrentModificationException. + * Bugfix: the contents of Comment nodes were not returned by Element.data() + *** Release 1.10.1 [2016-Oct-23] * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index e9e653d826..caf111072c 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1036,6 +1036,9 @@ public String data() { if (childNode instanceof DataNode) { DataNode data = (DataNode) childNode; sb.append(data.getWholeData()); + } else if (childNode instanceof Comment) { + Comment comment = (Comment) childNode; + sb.append(comment.getData()); } else if (childNode instanceof Element) { Element element = (Element) childNode; String elementData = element.data(); diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index ea3f1af0c7..d363f16247 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -658,7 +658,28 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":contains(%s", searchText); + return String.format(":contains(%s)", searchText); + } + } + + /** + * Evaluator for matching Element (and its descendants) data + */ + public static final class ContainsData extends Evaluator { + private String searchText; + + public ContainsData(String searchText) { + this.searchText = searchText.toLowerCase(); + } + + @Override + public boolean matches(Element root, Element element) { + return (element.data().toLowerCase().contains(searchText)); + } + + @Override + public String toString() { + return String.format(":containsData(%s)", searchText); } } @@ -679,7 +700,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":containsOwn(%s", searchText); + return String.format(":containsOwn(%s)", searchText); } } @@ -701,7 +722,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":matches(%s", pattern); + return String.format(":matches(%s)", pattern); } } @@ -723,7 +744,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":matchesOwn(%s", pattern); + return String.format(":matchesOwn(%s)", pattern); } } } diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 431468936d..6ba1fddabf 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -162,6 +162,8 @@ else if (tq.matches(":contains(")) contains(false); else if (tq.matches(":containsOwn(")) contains(true); + else if (tq.matches(":containsData(")) + containsData(); else if (tq.matches(":matches(")) matches(false); else if (tq.matches(":matchesOwn(")) @@ -339,6 +341,14 @@ private void contains(boolean own) { evals.add(new Evaluator.ContainsText(searchText)); } + // pseudo selector :containsData(data) + private void containsData() { + tq.consume(":containsData"); + String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); + Validate.notEmpty(searchText, ":containsData(text) query must not be empty"); + evals.add(new Evaluator.ContainsData(searchText)); + } + // :matches(regex), matchesOwn(regex) private void matches(boolean own) { tq.consume(own ? ":matchesOwn" : ":matches"); diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 1870147335..715508fd07 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -52,6 +52,7 @@ * :matches(regex)elements whose text matches the specified regular expression. The text may appear in the found element, or any of its descendants.td:matches(\\d+) finds table cells containing digits. div:matches((?i)login) finds divs containing the text, case insensitively. * :containsOwn(text)elements that directly contain the specified text. The search is case insensitive. The text must appear in the found element, not any of its descendants.p:containsOwn(jsoup) finds p elements with own text "jsoup". * :matchesOwn(regex)elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.td:matchesOwn(\\d+) finds table cells directly containing digits. div:matchesOwn((?i)login) finds divs containing the text, case insensitively. + * :containsData(data)elements that contains the specified data. The contents of {@code script} and {@code style} elements, and {@code comment} nodes (etc) are considered data nodes, not text nodes. The search is case insensitive. The data may appear in the found element, or any of its descendants.script:contains(jsoup) finds script elements containing the data "jsoup". * The above may be combined in any order and with other selectors.light:contains(name):eq(0) *

Structural pseudo selectors

* :rootThe element that is the root of the document. In HTML, this is the html element:root diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 4f648fb6b9..9b85fa364c 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -692,4 +692,26 @@ public void selectClassWithSpace() { assertEquals("One", doc.select("div[data=\"End]\"").first().text()); assertEquals("Two", doc.select("div[data=\"[Another)]]\"").first().text()); } + + @Test public void containsData() { + String html = "

jsoup

"; + Document doc = Jsoup.parse(html); + Element body = doc.body(); + + Elements dataEls1 = body.select(":containsData(jsoup)"); + Elements dataEls2 = body.select("script:containsData(jsoup)"); + Elements dataEls3 = body.select("span:containsData(comments)"); + Elements dataEls4 = body.select(":containsData(s)"); + + assertEquals(2, dataEls1.size()); // body and script + assertEquals(1, dataEls2.size()); + assertEquals(dataEls1.last(), dataEls2.first()); + assertEquals("", dataEls2.outerHtml()); + assertEquals(1, dataEls3.size()); + assertEquals("span", dataEls3.first().tagName()); + assertEquals(3, dataEls4.size()); + assertEquals("body", dataEls4.first().tagName()); + assertEquals("script", dataEls4.get(1).tagName()); + assertEquals("span", dataEls4.get(2).tagName()); + } } From 622caa2ac9742eb8985c35430b58efc352e9a05d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 30 Oct 2016 13:47:25 -0700 Subject: [PATCH 066/774] Better case insensitive test --- src/test/java/org/jsoup/select/SelectorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 9b85fa364c..0b380b3b7e 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -327,10 +327,10 @@ public class SelectorTest { String h = "
"; // mixed case so a simple toLowerCase() on value doesn't catch Document doc = Jsoup.parse(h); - assertEquals(2, doc.select("DIV").size()); - assertEquals(1, doc.select("DIV[TITLE]").size()); - assertEquals(1, doc.select("DIV[TITLE=BAR]").size()); - assertEquals(0, doc.select("DIV[TITLE=BARBARELLA").size()); + assertEquals(2, doc.select("DiV").size()); + assertEquals(1, doc.select("DiV[TiTLE]").size()); + assertEquals(1, doc.select("DiV[TiTLE=BAR]").size()); + assertEquals(0, doc.select("DiV[TiTLE=BARBARELLA").size()); } @Test public void adjacentSiblings() { From 0b75415dd58f16c2f0fce92cf39e5429ef0ae9f3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 30 Oct 2016 13:55:00 -0700 Subject: [PATCH 067/774] Test for #635 --- src/test/java/org/jsoup/select/QueryParserTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/jsoup/select/QueryParserTest.java b/src/test/java/org/jsoup/select/QueryParserTest.java index 6db844d2aa..5bf3d562db 100644 --- a/src/test/java/org/jsoup/select/QueryParserTest.java +++ b/src/test/java/org/jsoup/select/QueryParserTest.java @@ -39,4 +39,13 @@ public class QueryParserTest { assertEquals("li :prevli :ImmediateParentol", andRight.toString()); assertEquals(2, andLeft.evaluators.size()); } + + @Test public void exceptionOnUncloseAttribute() { + boolean threw = false; + try {Evaluator parse = QueryParser.parse("section > a[href=\"");} + catch (IllegalArgumentException e) { + threw = true; + } + assertTrue(threw); + } } From 188c3be9051e7b2a8012ea1f08ad2b46005f58d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCrten?= Date: Mon, 7 Nov 2016 00:47:18 +0100 Subject: [PATCH 068/774] Fixed a few typos --- src/main/java/org/jsoup/Connection.java | 6 +++--- src/main/java/org/jsoup/helper/StringUtil.java | 10 +++++----- src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/main/java/org/jsoup/nodes/Document.java | 2 +- src/main/java/org/jsoup/nodes/Entities.java | 2 +- src/main/java/org/jsoup/parser/Tag.java | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index c71189a7e7..cffe3d62e1 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -140,7 +140,7 @@ public final boolean hasBody() { Connection ignoreContentType(boolean ignoreContentType); /** - * Disable/enable TSL certificates validation for HTTPS requests. + * Disable/enable TLS certificates validation for HTTPS requests. *

* By default this is true; all * connections over HTTPS perform normal validation of certificates, and will abort requests if the provided @@ -153,7 +153,7 @@ public final boolean hasBody() { *

* Be careful and understand why you need to disable these validations. *

- * @param value if should validate TSL (SSL) certificates. true by default. + * @param value if should validate TLS (SSL) certificates. true by default. * @return this Connection, for chaining */ Connection validateTLSCertificates(boolean value); @@ -168,7 +168,7 @@ public final boolean hasBody() { Connection data(String key, String value); /** - * Add an input stream as a request data paramater. For GETs, has no effect, but for POSTS this will upload the + * Add an input stream as a request data parameter. For GETs, has no effect, but for POSTS this will upload the * input stream. * @param key data key (form item name) * @param filename the name of the file to present to the remove server. Typically just the name, not path, diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index ee96b6a488..f0dd8303de 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -14,7 +14,7 @@ public final class StringUtil { private static final String[] padding = {"", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}; /** - * Join a collection of strings by a seperator + * Join a collection of strings by a separator * @param strings collection of string objects * @param sep string to place between strings * @return joined string @@ -24,7 +24,7 @@ public static String join(Collection strings, String sep) { } /** - * Join a collection of strings by a seperator + * Join a collection of strings by a separator * @param strings iterator of string objects * @param sep string to place between strings * @return joined string @@ -64,7 +64,7 @@ public static String padding(int width) { } /** - * Tests if a string is blank: null, emtpy, or only whitespace (" ", \r\n, \t, etc) + * Tests if a string is blank: null, empty, or only whitespace (" ", \r\n, \t, etc) * @param string string to test * @return if string is blank */ @@ -83,7 +83,7 @@ public static boolean isBlank(String string) { /** * Tests if a string is numeric, i.e. contains only digit characters * @param string string to test - * @return true if only digit chars, false if empty or null or contains non-digit chrs + * @return true if only digit chars, false if empty or null or contains non-digit chars */ public static boolean isNumeric(String string) { if (string == null || string.length() == 0) @@ -160,7 +160,7 @@ public static boolean inSorted(String needle, String[] haystack) { /** * Create a new absolute URL, from a provided existing absolute URL and a relative URL component. - * @param base the existing absolulte base URL + * @param base the existing absolute base URL * @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned) * @return the resolved absolute URL * @throws MalformedURLException if an error occurred generating the URL diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 25ca4cec97..8a879ea73e 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -122,7 +122,7 @@ protected boolean isDataAttribute() { /** * Collapsible if it's a boolean attribute and value is empty or same as name * - * @param out Outputsettings + * @param out output settings * @return Returns whether collapsible or not */ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index b87fc18cc5..7ab9d912f3 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -296,7 +296,7 @@ public Document clone() { * true, otherwise this method does nothing. * *
    - *
  • An exsiting element gets updated with the current charset
  • + *
  • An existing element gets updated with the current charset
  • *
  • If there's no element yet it will be inserted
  • *
  • Obsolete elements are removed
  • *
diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index fbb2ca1ef9..ed38bda903 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -112,7 +112,7 @@ public static Character getCharacterByName(String name) { } /** - * Get the character(s) represented by the named entitiy + * Get the character(s) represented by the named entity * * @param name entity (e.g. "lt" or "amp") * @return the string value of the character(s) represented by this entity, or "" if not defined diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 646ba9b9d4..f99e282e2f 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -164,7 +164,7 @@ public static boolean isKnownTag(String tagName) { /** * Get if this tag should preserve whitespace within child text nodes. * - * @return if preserve whitepace + * @return if preserve whitespace */ public boolean preserveWhitespace() { return preserveWhitespace; From 69073d98de01dbfc5560068b4d2cd705c5933a4b Mon Sep 17 00:00:00 2001 From: nirro01 Date: Thu, 3 Nov 2016 09:24:57 +0200 Subject: [PATCH 069/774] Update Document.java cosmetic change in createShell --- src/main/java/org/jsoup/nodes/Document.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 7ab9d912f3..62197da5f4 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -37,7 +37,7 @@ public Document(String baseUri) { @param baseUri baseUri of document @return document with html, head, and body elements. */ - static public Document createShell(String baseUri) { + public static Document createShell(String baseUri) { Validate.notNull(baseUri); Document doc = new Document(baseUri); From b919f01e4719631f2621c523d78777ba237be7dd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 24 Nov 2016 12:13:16 -0800 Subject: [PATCH 070/774] In Whitelists, validate that removed protocol exists before removing. --- CHANGES | 2 + src/main/java/org/jsoup/safety/Whitelist.java | 113 +++++++++--------- .../java/org/jsoup/safety/CleanerTest.java | 11 ++ 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/CHANGES b/CHANGES index 26b5fa6e17..3d8db865ef 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,8 @@ jsoup changelog * Increased the default connect/read timeout in Jsoup.Connect to 30 seconds. + * In Whitelists, validate that a removed protocol exists before removing said protocol. + * Jsoup.Connect now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts the header value appropriately. This improves compatibility with servers that are configured incorrectly. diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index f641308cb3..bf05fa73db 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -245,27 +245,27 @@ public Whitelist removeTags(String... tags) {

@param tag The tag the attributes are for. The tag will be added to the allowed tag list if necessary. - @param keys List of valid attributes for the tag + @param attributes List of valid attributes for the tag @return this (for chaining) */ - public Whitelist addAttributes(String tag, String... keys) { + public Whitelist addAttributes(String tag, String... attributes) { Validate.notEmpty(tag); - Validate.notNull(keys); - Validate.isTrue(keys.length > 0, "No attributes supplied."); + Validate.notNull(attributes); + Validate.isTrue(attributes.length > 0, "No attribute names supplied."); TagName tagName = TagName.valueOf(tag); if (!tagNames.contains(tagName)) tagNames.add(tagName); Set attributeSet = new HashSet(); - for (String key : keys) { + for (String key : attributes) { Validate.notEmpty(key); attributeSet.add(AttributeKey.valueOf(key)); } - if (attributes.containsKey(tagName)) { - Set currentSet = attributes.get(tagName); + if (this.attributes.containsKey(tagName)) { + Set currentSet = this.attributes.get(tagName); currentSet.addAll(attributeSet); } else { - attributes.put(tagName, attributeSet); + this.attributes.put(tagName, attributeSet); } return this; } @@ -282,60 +282,60 @@ public Whitelist addAttributes(String tag, String... keys) {

@param tag The tag the attributes are for. - @param keys List of invalid attributes for the tag + @param attributes List of invalid attributes for the tag @return this (for chaining) */ - public Whitelist removeAttributes(String tag, String... keys) { + public Whitelist removeAttributes(String tag, String... attributes) { Validate.notEmpty(tag); - Validate.notNull(keys); - Validate.isTrue(keys.length > 0, "No attributes supplied."); + Validate.notNull(attributes); + Validate.isTrue(attributes.length > 0, "No attribute names supplied."); TagName tagName = TagName.valueOf(tag); Set attributeSet = new HashSet(); - for (String key : keys) { + for (String key : attributes) { Validate.notEmpty(key); attributeSet.add(AttributeKey.valueOf(key)); } - if(tagNames.contains(tagName) && attributes.containsKey(tagName)) { // Only look in sub-maps if tag was allowed - Set currentSet = attributes.get(tagName); + if(tagNames.contains(tagName) && this.attributes.containsKey(tagName)) { // Only look in sub-maps if tag was allowed + Set currentSet = this.attributes.get(tagName); currentSet.removeAll(attributeSet); if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag - attributes.remove(tagName); + this.attributes.remove(tagName); } if(tag.equals(":all")) // Attribute needs to be removed from all individually set tags - for(TagName name: attributes.keySet()) { - Set currentSet = attributes.get(name); + for(TagName name: this.attributes.keySet()) { + Set currentSet = this.attributes.get(name); currentSet.removeAll(attributeSet); if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag - attributes.remove(name); + this.attributes.remove(name); } return this; } /** Add an enforced attribute to a tag. An enforced attribute will always be added to the element. If the element - already has the attribute set, it will be overridden. + already has the attribute set, it will be overridden with this value.

E.g.: addEnforcedAttribute("a", "rel", "nofollow") will make all a tags output as <a href="..." rel="nofollow">

@param tag The tag the enforced attribute is for. The tag will be added to the allowed tag list if necessary. - @param key The attribute key + @param attribute The attribute name @param value The enforced attribute value @return this (for chaining) */ - public Whitelist addEnforcedAttribute(String tag, String key, String value) { + public Whitelist addEnforcedAttribute(String tag, String attribute, String value) { Validate.notEmpty(tag); - Validate.notEmpty(key); + Validate.notEmpty(attribute); Validate.notEmpty(value); TagName tagName = TagName.valueOf(tag); if (!tagNames.contains(tagName)) tagNames.add(tagName); - AttributeKey attrKey = AttributeKey.valueOf(key); + AttributeKey attrKey = AttributeKey.valueOf(attribute); AttributeValue attrVal = AttributeValue.valueOf(value); if (enforcedAttributes.containsKey(tagName)) { @@ -352,16 +352,16 @@ public Whitelist addEnforcedAttribute(String tag, String key, String value) { Remove a previously configured enforced attribute from a tag. @param tag The tag the enforced attribute is for. - @param key The attribute key + @param attribute The attribute name @return this (for chaining) */ - public Whitelist removeEnforcedAttribute(String tag, String key) { + public Whitelist removeEnforcedAttribute(String tag, String attribute) { Validate.notEmpty(tag); - Validate.notEmpty(key); + Validate.notEmpty(attribute); TagName tagName = TagName.valueOf(tag); if(tagNames.contains(tagName) && enforcedAttributes.containsKey(tagName)) { - AttributeKey attrKey = AttributeKey.valueOf(key); + AttributeKey attrKey = AttributeKey.valueOf(attribute); Map attrMap = enforcedAttributes.get(tagName); attrMap.remove(attrKey); @@ -403,17 +403,17 @@ public Whitelist preserveRelativeLinks(boolean preserve) {

@param tag Tag the URL protocol is for - @param key Attribute key + @param attribute Attribute name @param protocols List of valid protocols @return this, for chaining */ - public Whitelist addProtocols(String tag, String key, String... protocols) { + public Whitelist addProtocols(String tag, String attribute, String... protocols) { Validate.notEmpty(tag); - Validate.notEmpty(key); + Validate.notEmpty(attribute); Validate.notNull(protocols); TagName tagName = TagName.valueOf(tag); - AttributeKey attrKey = AttributeKey.valueOf(key); + AttributeKey attrKey = AttributeKey.valueOf(attribute); Map> attrMap; Set protSet; @@ -438,40 +438,41 @@ public Whitelist addProtocols(String tag, String key, String... protocols) { } /** - Remove allowed URL protocols for an element's URL attribute. + Remove allowed URL protocols for an element's URL attribute. If you remove all protocols for an attribute, that + attribute will allow any protocol.

E.g.: removeProtocols("a", "href", "ftp")

- @param tag Tag the URL protocol is for - @param key Attribute key - @param protocols List of invalid protocols + @param tag Tag the URL protocol is for + @param attribute Attribute name + @param removeProtocols List of invalid protocols @return this, for chaining */ - public Whitelist removeProtocols(String tag, String key, String... protocols) { + public Whitelist removeProtocols(String tag, String attribute, String... removeProtocols) { Validate.notEmpty(tag); - Validate.notEmpty(key); - Validate.notNull(protocols); + Validate.notEmpty(attribute); + Validate.notNull(removeProtocols); TagName tagName = TagName.valueOf(tag); - AttributeKey attrKey = AttributeKey.valueOf(key); - - if(this.protocols.containsKey(tagName)) { - Map> attrMap = this.protocols.get(tagName); - if(attrMap.containsKey(attrKey)) { - Set protSet = attrMap.get(attrKey); - for (String protocol : protocols) { - Validate.notEmpty(protocol); - Protocol prot = Protocol.valueOf(protocol); - protSet.remove(prot); - } + AttributeKey attr = AttributeKey.valueOf(attribute); - if(protSet.isEmpty()) { // Remove protocol set if empty - attrMap.remove(attrKey); - if(attrMap.isEmpty()) // Remove entry for tag if empty - this.protocols.remove(tagName); - } - } + // make sure that what we're removing actually exists; otherwise can open the tag to any data and that can + // be surprising + Validate.isTrue(protocols.containsKey(tagName), "Cannot remove a protocol that is not set."); + Map> tagProtocols = protocols.get(tagName); + Validate.isTrue(tagProtocols.containsKey(attr), "Cannot remove a protocol that is not set."); + + Set attrProtocols = tagProtocols.get(attr); + for (String protocol : removeProtocols) { + Validate.notEmpty(protocol); + attrProtocols.remove(Protocol.valueOf(protocol)); + } + + if (attrProtocols.isEmpty()) { // Remove protocol set if empty + tagProtocols.remove(attr); + if (tagProtocols.isEmpty()) // Remove entry for tag if empty + protocols.remove(tagName); } return this; } diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 70bcfa696f..18232d4f8f 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -5,6 +5,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Entities; import org.junit.Test; + import static org.junit.Assert.*; /** @@ -242,4 +243,14 @@ public void testScriptTagInWhiteList() { whitelist.addTags( "script" ); assertTrue( Jsoup.isValid("HelloWorld !", whitelist ) ); } + + @Test(expected = IllegalArgumentException.class) + public void bailsIfRemovingProtocolThatsNotSet() { + // a case that came up on the email list + Whitelist w = Whitelist.none(); + + // note no add tag, and removing protocol without adding first + w.addAttributes("a", "href"); + w.removeProtocols("a", "href", "javascript"); // with no protocols enforced, this was a noop. Now validates. + } } From f44d6e64ac97d4a5c119e3e22f22f4d87c94b7e1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 24 Nov 2016 14:36:13 -0800 Subject: [PATCH 071/774] Update isValid and cleaner methods Stricter at enforcing that it is only body content and that there are no HTML errors. Fixes #245 Fixes #632 --- CHANGES | 6 ++++ src/main/java/org/jsoup/Jsoup.java | 15 ++++----- .../java/org/jsoup/parser/ParseErrorList.java | 6 ++-- src/main/java/org/jsoup/parser/Parser.java | 16 ++++++++++ src/main/java/org/jsoup/safety/Cleaner.java | 31 ++++++++++++++++--- src/main/java/org/jsoup/safety/Whitelist.java | 26 ++++++++++------ .../java/org/jsoup/safety/CleanerTest.java | 28 +++++++++++++++-- 7 files changed, 102 insertions(+), 26 deletions(-) diff --git a/CHANGES b/CHANGES index 3d8db865ef..077c6f5215 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,12 @@ jsoup changelog * In Whitelists, validate that a removed protocol exists before removing said protocol. + * Changed Jsoup.isValid(bodyHtml) to validate that the input contains only body HTML that is safe according to the + whitelist, and does not include HTML errors. And in the Jsoup.Cleaner.isValid(Document) method, make sure the doc + only includes body HTML. + + + * Jsoup.Connect now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts the header value appropriately. This improves compatibility with servers that are configured incorrectly. diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 3252daffdc..84a5e34efd 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -217,8 +217,10 @@ public static String clean(String bodyHtml, Whitelist whitelist) { /** * Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of - * permitted - * tags and attributes. + * permitted tags and attributes. + *

The HTML is treated as a body fragment; it's expected the cleaned HTML will be used within the body of an + * existing document. If you want to clean full documents, use {@link Cleaner#clean(Document)} instead, and add + * structural tags (html, head, body etc) to the whitelist. * * @param bodyHtml input untrusted HTML (body fragment) * @param baseUri URL to resolve relative URLs against @@ -236,17 +238,16 @@ public static String clean(String bodyHtml, String baseUri, Whitelist whitelist, } /** - Test if the input HTML has only tags and attributes allowed by the Whitelist. Useful for form validation. The input HTML should - still be run through the cleaner to set up enforced attributes, and to tidy the output. + Test if the input body HTML has only tags and attributes allowed by the Whitelist. Useful for form validation. +

The input HTML should still be run through the cleaner to set up enforced attributes, and to tidy the output. +

Assumes the HTML is a body fragment (i.e. will be used in an existing HTML document body.) @param bodyHtml HTML to test @param whitelist whitelist to test against @return true if no tags or attributes were removed; false otherwise @see #clean(String, org.jsoup.safety.Whitelist) */ public static boolean isValid(String bodyHtml, Whitelist whitelist) { - Document dirty = parseBodyFragment(bodyHtml, ""); - Cleaner cleaner = new Cleaner(whitelist); - return cleaner.isValid(dirty); + return new Cleaner(whitelist).isValidBodyHtml(bodyHtml); } } diff --git a/src/main/java/org/jsoup/parser/ParseErrorList.java b/src/main/java/org/jsoup/parser/ParseErrorList.java index 3824ffbc4e..b2ece0c5d6 100644 --- a/src/main/java/org/jsoup/parser/ParseErrorList.java +++ b/src/main/java/org/jsoup/parser/ParseErrorList.java @@ -7,7 +7,7 @@ * * @author Jonathan Hedley */ -class ParseErrorList extends ArrayList{ +public class ParseErrorList extends ArrayList{ private static final int INITIAL_CAPACITY = 16; private final int maxSize; @@ -24,11 +24,11 @@ int getMaxSize() { return maxSize; } - static ParseErrorList noTracking() { + public static ParseErrorList noTracking() { return new ParseErrorList(0, 0); } - static ParseErrorList tracking(int maxSize) { + public static ParseErrorList tracking(int maxSize) { return new ParseErrorList(INITIAL_CAPACITY, maxSize); } } diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index 868ef41e4f..0751c22999 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -115,6 +115,22 @@ public static List parseFragment(String fragmentHtml, Element context, Str return treeBuilder.parseFragment(fragmentHtml, context, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); } + /** + * Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context. + * + * @param fragmentHtml the fragment of HTML to parse + * @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This + * provides stack context (for implicit element creation). + * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs. + * @param errorList list to add errors to + * + * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified. + */ + public static List parseFragment(String fragmentHtml, Element context, String baseUri, ParseErrorList errorList) { + HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder(); + return treeBuilder.parseFragment(fragmentHtml, context, baseUri, errorList, treeBuilder.defaultSettings()); + } + /** * Parse a fragment of XML into a list of nodes. * diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 3922a90b2c..1223bbe800 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -1,11 +1,21 @@ package org.jsoup.safety; import org.jsoup.helper.Validate; -import org.jsoup.nodes.*; +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; +import org.jsoup.parser.ParseErrorList; +import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; +import java.util.List; + /** The whitelist based HTML cleaner. Use to ensure that end-user provided HTML contains only the elements and attributes @@ -51,10 +61,10 @@ public Document clean(Document dirtyDocument) { } /** - Determines if the input document is valid, against the whitelist. It is considered valid if all the tags and attributes - in the input HTML are allowed by the whitelist. + Determines if the input document bodyis valid, against the whitelist. It is considered valid if all the tags and attributes + in the input HTML are allowed by the whitelist, and that there is no content in the head.

- This method can be used as a validator for user input forms. An invalid document will still be cleaned successfully + This method can be used as a validator for user input. An invalid document will still be cleaned successfully using the {@link #clean(Document)} document. If using as a validator, it is recommended to still clean the document to ensure enforced attributes are set correctly, and that the output is tidied.

@@ -66,7 +76,18 @@ public boolean isValid(Document dirtyDocument) { Document clean = Document.createShell(dirtyDocument.baseUri()); int numDiscarded = copySafeNodes(dirtyDocument.body(), clean.body()); - return numDiscarded == 0; + return numDiscarded == 0 + && dirtyDocument.head().childNodes().size() == 0; // because we only look at the body, but we start from a shell, make sure there's nothing in the head + } + + public boolean isValidBodyHtml(String bodyHtml) { + Document clean = Document.createShell(""); + Document dirty = Document.createShell(""); + ParseErrorList errorList = ParseErrorList.tracking(1); + List nodes = Parser.parseFragment(bodyHtml, dirty.body(), "", errorList); + dirty.body().insertChildren(0, nodes); + int numDiscarded = copySafeNodes(dirty.body(), clean.body()); + return numDiscarded == 0 && errorList.size() == 0; } /** diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index bf05fa73db..b8d0886008 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -497,15 +497,23 @@ protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) { TagName tag = TagName.valueOf(tagName); AttributeKey key = AttributeKey.valueOf(attr.getKey()); - if (attributes.containsKey(tag)) { - if (attributes.get(tag).contains(key)) { - if (protocols.containsKey(tag)) { - Map> attrProts = protocols.get(tag); - // ok if not defined protocol; otherwise test - return !attrProts.containsKey(key) || testValidProtocol(el, attr, attrProts.get(key)); - } else { // attribute found, no protocols defined, so OK - return true; - } + Set okSet = attributes.get(tag); + if (okSet != null && okSet.contains(key)) { + if (protocols.containsKey(tag)) { + Map> attrProts = protocols.get(tag); + // ok if not defined protocol; otherwise test + return !attrProts.containsKey(key) || testValidProtocol(el, attr, attrProts.get(key)); + } else { // attribute found, no protocols defined, so OK + return true; + } + } + // might be an enforced attribute? + Map enforcedSet = enforcedAttributes.get(tag); + if (enforcedSet != null) { + Attributes expect = getEnforcedAttributes(tagName); + String attrKey = attr.getKey(); + if (expect.hasKeyIgnoreCase(attrKey)) { + return expect.getIgnoreCase(attrKey).equals(attr.getValue()); } } // no attributes defined for tag, try :all tag diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 18232d4f8f..8f159ce70d 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -3,6 +3,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.jsoup.nodes.Entities; import org.junit.Test; @@ -141,15 +142,38 @@ public class CleanerTest { assertEquals("\"\"", cleanHtml); } - @Test public void testIsValid() { - String ok = "

Test OK

"; + @Test public void testIsValidBodyHtml() { + String ok = "

Test OK

"; + String ok1 = "

Test OK

"; // missing enforced is OK because still needs run thru cleaner String nok1 = "

Not OK

"; String nok2 = "

Test Not OK

"; String nok3 = "

Not OK

"; // comments and the like will be cleaned + String nok4 = "Foo OK"; // not body html + String nok5 = "

Test OK

"; + String nok6 = "

Test OK

"; // missing close tag + String nok7 = "
What"; assertTrue(Jsoup.isValid(ok, Whitelist.basic())); + assertTrue(Jsoup.isValid(ok1, Whitelist.basic())); assertFalse(Jsoup.isValid(nok1, Whitelist.basic())); assertFalse(Jsoup.isValid(nok2, Whitelist.basic())); assertFalse(Jsoup.isValid(nok3, Whitelist.basic())); + assertFalse(Jsoup.isValid(nok4, Whitelist.basic())); + assertFalse(Jsoup.isValid(nok5, Whitelist.basic())); + assertFalse(Jsoup.isValid(nok6, Whitelist.basic())); + assertFalse(Jsoup.isValid(ok, Whitelist.none())); + assertFalse(Jsoup.isValid(nok7, Whitelist.basic())); + } + + @Test public void testIsValidDocument() { + String ok = "

Hello

"; + String nok = "Hello

Hello

"; + + Whitelist relaxed = Whitelist.relaxed(); + Cleaner cleaner = new Cleaner(relaxed); + Document okDoc = Jsoup.parse(ok); + assertTrue(cleaner.isValid(okDoc)); + assertFalse(cleaner.isValid(Jsoup.parse(nok))); + assertFalse(new Cleaner(Whitelist.none()).isValid(okDoc)); } @Test public void resolvesRelativeLinks() { From 79b1aa4b580534ef6ef7057d5b3b4a8250b8c5e5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 11 Dec 2016 12:27:11 -0800 Subject: [PATCH 072/774] Trim \r on entity load --- CHANGES | 2 ++ src/main/java/org/jsoup/nodes/Entities.java | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 077c6f5215..397bf28704 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,8 @@ jsoup changelog * Bugfix: the contents of Comment nodes were not returned by Element.data() + * Bugfix: if source checked out on Windows with git autocrlf=true, Entities.load would fail because of the \r char. + *** Release 1.10.1 [2016-Oct-23] * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index ed38bda903..d7e5d4999b 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -321,7 +321,12 @@ private static void load(EscapeMode e, String file, int size) { } else { cp2 = empty; } - final int index = Integer.parseInt(reader.consumeTo('\n'), codepointRadix); + String indexS = reader.consumeTo('\n'); + // default git checkout on windows will add a \r there, so remove + if (indexS.charAt(indexS.length() - 1) == '\r') { + indexS = indexS.substring(0, indexS.length() - 1); + } + final int index = Integer.parseInt(indexS, codepointRadix); reader.advance(); e.nameKeys[i] = name; From 577b15456855dc9da534200920aa7552b8421580 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 1 Jan 2017 18:05:56 -0800 Subject: [PATCH 073/774] Happy New Year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 9e15540218..ab9f00b359 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -© 2009-2016, Jonathan Hedley +© 2009-2017, Jonathan Hedley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From d79f498bb1b60e6527b09ddca7a4913f1314e768 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 2 Jan 2017 11:05:59 -0800 Subject: [PATCH 074/774] Changelog release prep --- CHANGES | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 397bf28704..0e1bbdcd4c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.10.2 [PENDING] +*** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. @@ -13,24 +13,24 @@ jsoup changelog * Added the new selector :containsData(), to find elements that hold data, like script and style tags. + * Changed Jsoup.isValid(bodyHtml) to validate that the input contains only body HTML that is safe according to the + whitelist, and does not include HTML errors. And in the Jsoup.Cleaner.isValid(Document) method, make sure the doc + only includes body HTML. + + + + * In Whitelists, validate that a removed protocol exists before removing said protocol. + * Allow the Jsoup.Connect thread to be interrupted when reading the input stream; helps when reading from a long stream of data that doesn't read timeout. - * Jsoup.Connect now uses a desktop user-agent by default. Many users were getting caught by not specifying a - user-agent, and so the default of 'Java' was used. That causes many servers to return different content than what - they would to a desktop browser, and what the developer was expecting. + * Jsoup.Connect now uses a desktop user agent by default. Many developers were getting caught by not specifying the + user agent, and sending the default 'Java'. That causes many servers to return different content than what they would + to a desktop browser, and what the developer was expecting. * Increased the default connect/read timeout in Jsoup.Connect to 30 seconds. - * In Whitelists, validate that a removed protocol exists before removing said protocol. - - * Changed Jsoup.isValid(bodyHtml) to validate that the input contains only body HTML that is safe according to the - whitelist, and does not include HTML errors. And in the Jsoup.Cleaner.isValid(Document) method, make sure the doc - only includes body HTML. - - - * Jsoup.Connect now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts the header value appropriately. This improves compatibility with servers that are configured incorrectly. From d1ade46486a125742a05fa8e245ed5d0f3d572ad Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 2 Jan 2017 11:09:50 -0800 Subject: [PATCH 075/774] [maven-release-plugin] prepare release jsoup-1.10.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0b44b417ba..78f7585e1e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.10.2-SNAPSHOT + 1.10.2 jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.10.2 Jonathan Hedley From 7f6b23ffe1ad37865755f927c3f1f5dc5b943c2d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 2 Jan 2017 11:09:57 -0800 Subject: [PATCH 076/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 78f7585e1e..2cba88001b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.10.2 + 1.10.3-SNAPSHOT jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.10.2 + HEAD Jonathan Hedley From d8eb9bd63c861132e5307a65e8f2f234fab2416b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 5 Jan 2017 13:10:55 -0800 Subject: [PATCH 077/774] Added tests for children() including empty children --- .../java/org/jsoup/nodes/ElementTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 458afd9445..af7643d00d 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1001,4 +1001,33 @@ public void testIs() { Element a = new Element("P"); assertTrue(a.tagName().equals("P")); } + + public void testChildrenElements() { + String html = "

One

Two

Three
Four"; + Document doc = Jsoup.parse(html); + Element div = doc.select("div").first(); + Element p = doc.select("p").first(); + Element span = doc.select("span").first(); + Element foo = doc.select("foo").first(); + Element img = doc.select("img").first(); + + Elements docChildren = div.children(); + assertEquals(2, docChildren.size()); + assertEquals("

One

", docChildren.get(0).outerHtml()); + assertEquals("

Two

", docChildren.get(1).outerHtml()); + assertEquals(3, div.childNodes().size()); + assertEquals("Three", div.childNodes().get(2).outerHtml()); + + assertEquals(1, p.children().size()); + assertEquals("One", p.children().text()); + + assertEquals(0, span.children().size()); + assertEquals(1, span.childNodes().size()); + assertEquals("Four", span.childNodes().get(0).outerHtml()); + + assertEquals(0, foo.children().size()); + assertEquals(0, foo.childNodes().size()); + assertEquals(0, img.children().size()); + assertEquals(0, img.childNodes().size()); + } } From a623db776696c0e03f68e849147f6a0c57063c02 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 5 Jan 2017 15:07:29 -0800 Subject: [PATCH 078/774] Check attribute name is not empty after trimming before creating attribute Fixes #793 --- CHANGES | 5 +++++ src/main/java/org/jsoup/nodes/Attribute.java | 3 ++- src/main/java/org/jsoup/parser/Token.java | 22 +++++++++++-------- .../java/org/jsoup/parser/HtmlParserTest.java | 5 +++++ .../java/org/jsoup/safety/CleanerTest.java | 6 +++++ 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 0e1bbdcd4c..5d468af865 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,10 @@ jsoup changelog +*** Release 1.10.3 [PENDING] + * Bugfix: if an attribute name started or ended with a control character, the parse would fail with a validation + exception. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 8a879ea73e..38be8235cb 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -29,9 +29,10 @@ public class Attribute implements Map.Entry, Cloneable { * @see #createFromEncoded */ public Attribute(String key, String value) { - Validate.notEmpty(key); + Validate.notNull(key); Validate.notNull(value); this.key = key.trim(); + Validate.notEmpty(key); // trimming could potentially make empty, so validate here this.value = value; } diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 34baf19699..af51432173 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -102,15 +102,19 @@ final void newAttribute() { attributes = new Attributes(); if (pendingAttributeName != null) { - Attribute attribute; - if (hasPendingAttributeValue) - attribute = new Attribute(pendingAttributeName, - pendingAttributeValue.length() > 0 ? pendingAttributeValue.toString() : pendingAttributeValueS); - else if (hasEmptyAttributeValue) - attribute = new Attribute(pendingAttributeName, ""); - else - attribute = new BooleanAttribute(pendingAttributeName); - attributes.put(attribute); + // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here + pendingAttributeName = pendingAttributeName.trim(); + if (pendingAttributeName.length() > 0) { + Attribute attribute; + if (hasPendingAttributeValue) + attribute = new Attribute(pendingAttributeName, + pendingAttributeValue.length() > 0 ? pendingAttributeValue.toString() : pendingAttributeValueS); + else if (hasEmptyAttributeValue) + attribute = new Attribute(pendingAttributeName, ""); + else + attribute = new BooleanAttribute(pendingAttributeName); + attributes.put(attribute); + } } pendingAttributeName = null; hasEmptyAttributeValue = false; diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index b37daddf02..4ab935918c 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -941,4 +941,9 @@ public void testInvalidTableContents() throws IOException { Document doc = parser.parseInput("
", ""); assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); } + + @Test public void handlesControlCodeInAttributeName() { + Document doc = Jsoup.parse("

OneTwo

"); + assertEquals("

OneTwo

", doc.body().html()); + } } diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 8f159ce70d..9bc78e65f7 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -277,4 +277,10 @@ public void bailsIfRemovingProtocolThatsNotSet() { w.addAttributes("a", "href"); w.removeProtocols("a", "href", "javascript"); // with no protocols enforced, this was a noop. Now validates. } + + @Test public void handlesControlCharactersAfterTagName() { + String html = ""; + String clean = Jsoup.clean(html, Whitelist.basic()); + assertEquals("", clean); + } } From c221cc8ab03fe7a88982cdddb9e66cff546d29df Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 5 Jan 2017 15:36:30 -0800 Subject: [PATCH 079/774] Bit of a Tag cleanup Fixes #767 These fields are only advisory for other users now, and no longer used in tree construction (since the HTML5 builder rewrite). --- src/main/java/org/jsoup/parser/Tag.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index f99e282e2f..bebeaaee0b 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -16,7 +16,6 @@ public class Tag { private String tagName; private boolean isBlock = true; // block or inline private boolean formatAsBlock = true; // should be formatted as a block - private boolean canContainBlock = true; // Can this tag hold block level tags? private boolean canContainInline = true; // only pcdata if not private boolean empty = false; // can hold nothing; e.g. img private boolean selfClosing = false; // can self close (). used for unknown tags that self close, without forcing them as empty. @@ -60,7 +59,6 @@ public static Tag valueOf(String tagName, ParseSettings settings) { // not defined: create default; go anywhere, do anything! (incl be inside a

) tag = new Tag(tagName); tag.isBlock = false; - tag.canContainBlock = true; } } return tag; @@ -101,9 +99,10 @@ public boolean formatAsBlock() { * Gets if this tag can contain block tags. * * @return if tag can contain block tags + * @deprecated No longer used, and no different result than {{@link #isBlock()}} */ public boolean canContainBlock() { - return canContainBlock; + return isBlock; } /** @@ -199,7 +198,6 @@ public boolean equals(Object o) { Tag tag = (Tag) o; if (!tagName.equals(tag.tagName)) return false; - if (canContainBlock != tag.canContainBlock) return false; if (canContainInline != tag.canContainInline) return false; if (empty != tag.empty) return false; if (formatAsBlock != tag.formatAsBlock) return false; @@ -215,7 +213,6 @@ public int hashCode() { int result = tagName.hashCode(); result = 31 * result + (isBlock ? 1 : 0); result = 31 * result + (formatAsBlock ? 1 : 0); - result = 31 * result + (canContainBlock ? 1 : 0); result = 31 * result + (canContainInline ? 1 : 0); result = 31 * result + (empty ? 1 : 0); result = 31 * result + (selfClosing ? 1 : 0); @@ -236,7 +233,7 @@ public String toString() { "html", "head", "body", "frameset", "script", "noscript", "style", "meta", "link", "title", "frame", "noframes", "section", "nav", "aside", "hgroup", "header", "footer", "p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins", - "del", "s", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th", + "del", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th", "td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main", "svg", "math" }; @@ -246,7 +243,7 @@ public String toString() { "sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup", "option", "legend", "datalist", "keygen", "output", "progress", "meter", "area", "param", "source", "track", "summary", "command", "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track", - "data", "bdi" + "data", "bdi", "s" }; private static final String[] emptyTags = { "meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command", @@ -277,7 +274,6 @@ public String toString() { for (String tagName : inlineTags) { Tag tag = new Tag(tagName); tag.isBlock = false; - tag.canContainBlock = false; tag.formatAsBlock = false; register(tag); } @@ -286,7 +282,6 @@ public String toString() { for (String tagName : emptyTags) { Tag tag = tags.get(tagName); Validate.notNull(tag); - tag.canContainBlock = false; tag.canContainInline = false; tag.empty = true; } From 8fecf72da0c9d1d6da7ec1aab0214ff68588da88 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 5 Jan 2017 19:48:35 -0800 Subject: [PATCH 080/774] Improved CSS query parse validation Fixes #803 --- CHANGES | 3 +++ .../java/org/jsoup/parser/TokenQueue.java | 6 +++++- .../java/org/jsoup/select/QueryParser.java | 8 ++++++-- .../java/org/jsoup/integration/ParseTest.java | 2 +- .../java/org/jsoup/parser/TokenQueueTest.java | 4 ++-- .../org/jsoup/select/QueryParserTest.java | 13 ++++++------ .../java/org/jsoup/select/SelectorTest.java | 20 +++++++++++++------ 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index 5d468af865..146140396e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.10.3 [PENDING] + * Improved selector validation for :contains(...) with unbalanced quotes. + + * Bugfix: if an attribute name started or ended with a control character, the parse would fail with a validation exception. diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 4e04c72e9f..ffcb4a4290 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -285,7 +285,11 @@ else if (c.equals(close)) end = pos; // don't include the outer match pair in the return last = c; } while (depth > 0); - return (end >= 0) ? queue.substring(start, end) : ""; + final String out = (end >= 0) ? queue.substring(start, end) : ""; + if (depth > 0) {// ran out of queue before seeing enough ) + Validate.fail("Did not find balanced maker at " + out); + } + return out; } /** diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 6ba1fddabf..d3a20418da 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -35,8 +35,12 @@ private QueryParser(String query) { * @return Evaluator */ public static Evaluator parse(String query) { - QueryParser p = new QueryParser(query); - return p.parse(); + try { + QueryParser p = new QueryParser(query); + return p.parse(); + } catch (IllegalArgumentException e) { + throw new Selector.SelectorParseException(e.getMessage()); + } } /** diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index c5e6eae8cf..ae14bed2fe 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -164,7 +164,7 @@ public void testNytArticle() throws IOException { public void testYahooArticle() throws IOException { File in = getFile("/htmltests/yahoo-article-1.html"); Document doc = Jsoup.parse(in, "UTF-8", "http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china"); - Element p = doc.select("p:contains(Volt will be sold in the United States").first(); + Element p = doc.select("p:contains(Volt will be sold in the United States)").first(); assertEquals("In July, GM said its electric Chevrolet Volt will be sold in the United States at $41,000 -- $8,000 more than its nearest competitor, the Nissan Leaf.", p.text()); } diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java index 802d19bfae..d80a4e9d85 100644 --- a/src/test/java/org/jsoup/parser/TokenQueueTest.java +++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java @@ -31,10 +31,10 @@ public class TokenQueueTest { } @Test public void chompBalancedMatchesAsMuchAsPossible() { - TokenQueue tq = new TokenQueue("unbalanced(something(or another"); + TokenQueue tq = new TokenQueue("unbalanced(something(or another)) else"); tq.consumeTo("("); String match = tq.chompBalanced('(', ')'); - assertEquals("something(or another", match); + assertEquals("something(or another)", match); } @Test public void unescape() { diff --git a/src/test/java/org/jsoup/select/QueryParserTest.java b/src/test/java/org/jsoup/select/QueryParserTest.java index 5bf3d562db..0720e47c9f 100644 --- a/src/test/java/org/jsoup/select/QueryParserTest.java +++ b/src/test/java/org/jsoup/select/QueryParserTest.java @@ -40,12 +40,11 @@ public class QueryParserTest { assertEquals(2, andLeft.evaluators.size()); } - @Test public void exceptionOnUncloseAttribute() { - boolean threw = false; - try {Evaluator parse = QueryParser.parse("section > a[href=\"");} - catch (IllegalArgumentException e) { - threw = true; - } - assertTrue(threw); + @Test(expected = Selector.SelectorParseException.class) public void exceptionOnUncloseAttribute() { + Evaluator parse = QueryParser.parse("section > a[href=\"]"); + } + + @Test(expected = Selector.SelectorParseException.class) public void testParsesSingleQuoteInContains() { + Evaluator parse = QueryParser.parse("p:contains(One \" One)"); } } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 0b380b3b7e..c04a42a54b 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -330,7 +330,7 @@ public class SelectorTest { assertEquals(2, doc.select("DiV").size()); assertEquals(1, doc.select("DiV[TiTLE]").size()); assertEquals(1, doc.select("DiV[TiTLE=BAR]").size()); - assertEquals(0, doc.select("DiV[TiTLE=BARBARELLA").size()); + assertEquals(0, doc.select("DiV[TiTLE=BARBARELLA]").size()); } @Test public void adjacentSiblings() { @@ -472,7 +472,7 @@ public class SelectorTest { assertEquals("0", divs1.get(0).id()); assertEquals("1", divs1.get(1).id()); - Elements divs2 = doc.select("div:has([class]"); + Elements divs2 = doc.select("div:has([class])"); assertEquals(1, divs2.size()); assertEquals("1", divs2.get(0).id()); @@ -687,10 +687,10 @@ public void selectClassWithSpace() { @Test public void attributeWithBrackets() { String html = "

One
Two
"; Document doc = Jsoup.parse(html); - assertEquals("One", doc.select("div[data='End]'").first().text()); - assertEquals("Two", doc.select("div[data='[Another)]]'").first().text()); - assertEquals("One", doc.select("div[data=\"End]\"").first().text()); - assertEquals("Two", doc.select("div[data=\"[Another)]]\"").first().text()); + assertEquals("One", doc.select("div[data='End]']").first().text()); + assertEquals("Two", doc.select("div[data='[Another)]]']").first().text()); + assertEquals("One", doc.select("div[data=\"End]\"]").first().text()); + assertEquals("Two", doc.select("div[data=\"[Another)]]\"]").first().text()); } @Test public void containsData() { @@ -714,4 +714,12 @@ public void selectClassWithSpace() { assertEquals("script", dataEls4.get(1).tagName()); assertEquals("span", dataEls4.get(2).tagName()); } + + @Test public void containsWithQuote() { + String html = "

One'One

One'Two

"; + Document doc = Jsoup.parse(html); + Elements els = doc.select("p:contains(One\\'One)"); + assertEquals(1, els.size()); + assertEquals("One'One", els.text()); + } } From 040ce712b73fcb3ff16b7d8dda1246ccd23a6988 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 6 Jan 2017 09:46:11 -0800 Subject: [PATCH 081/774] Javadoc tweaks --- src/main/java/org/jsoup/Connection.java | 6 +++++- src/main/java/org/jsoup/parser/TokenQueue.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index cffe3d62e1..92ec55c7ed 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -84,7 +84,11 @@ public final boolean hasBody() { /** * Set the request timeouts (connect and read). If a timeout occurs, an IOException will be thrown. The default - * timeout is (30000 millis). A timeout of zero is treated as an infinite timeout. + * timeout is 30 seconds (30,000 millis). A timeout of zero is treated as an infinite timeout. + *

Note that a read timeout is not the same as a maximum timeout. As long as the connection is sending bytes at + * least every timeout seconds (e.g. in the case of an infinite stream of data, or a slow large download), the + * read timeout will not fire. This can be mitigated by using a maximum download size (see {@link #maxBodySize(int)}), + * or interrupting the connecting thread after a max timeout.

* @param millis number of milliseconds (thousandths of a second) before timing out connects or reads. * @return this Connection, for chaining */ diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index ffcb4a4290..57a23d79eb 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -250,7 +250,7 @@ public String chompToIgnoreCase(String seq) { /** * Pulls a balanced string off the queue. E.g. if queue is "(one (two) three) four", (,) will return "one (two) three", - * and leave " four" on the queue. Unbalanced openers and closers can quoted (with ' or ") or escaped (with \). Those escapes will be left + * and leave " four" on the queue. Unbalanced openers and closers can be quoted (with ' or ") or escaped (with \). Those escapes will be left * in the returned string, which is suitable for regexes (where we need to preserve the escape), but unsuitable for * contains text strings; use unescape for that. * @param open opener From c42928d100f6e30dcc31e1f01dff392c16793b7d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 6 Jan 2017 13:24:42 -0800 Subject: [PATCH 082/774] Updated readme to md, more detail --- README | 17 ----------------- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 17 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index 19bf7c4ff5..0000000000 --- a/README +++ /dev/null @@ -1,17 +0,0 @@ -jsoup: Java HTML parser that makes sense of real-world HTML soup. - -jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. - -jsoup implements the WHATWG HTML5 specification (http://whatwg.org/html), and parses HTML to the same DOM as modern browsers do. - -* parse HTML from a URL, file, or string -* find and extract data, using DOM traversal or CSS selectors -* manipulate the HTML elements, attributes, and text -* clean user-submitted content against a safe white-list, to prevent XSS -* output tidy HTML - -jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; jsoup will create a sensible parse tree. - -jsoup runs on Java 1.5 and up. - -See https://jsoup.org/ for downloads and documentation. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..345b718ed9 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# jsoup: Java HTML Parser + +**jsoup** is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. + + +**jsoup** implements the [WHATWG HTML5](http://whatwg.org/html) specification, and parses HTML to the same DOM as modern browsers do. + +* scrape and [parse](https://jsoup.org/cookbook/input/parse-document-from-string) HTML from a URL, file, or string +* find and [extract data](https://jsoup.org/cookbook/extracting-data/selector-syntax), using DOM traversal or CSS selectors +* manipulate the [HTML elements](https://jsoup.org/cookbook/modifying-data/set-html), attributes, and text +* [clean](https://jsoup.org/cookbook/cleaning-html/whitelist-sanitizer) user-submitted content against a safe white-list, to prevent XSS attacks +* output tidy HTML + +jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; jsoup will create a sensible parse tree. + +See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API documentation](https://jsoup.org/apidocs/). + +## Example +Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html) ([online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g)): + +```java +Document doc = Jsoup.connect("http://en.wikipedia.org/").get(); +Elements newsHeadlines = doc.select("#mp-itn b a"); +``` + +## Open source +jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available at [GitHub](https://github.com/jhy/jsoup/tree/master/src/main/java/org/jsoup). + +## Getting started +1. [Download](https://jsoup.org/download) the latest jsoup jar (or it add to your Maven/Gradle build) +2. Read the [cookbook](https://jsoup.org/cookbook/) +3. Enjoy! + +## Development and support +If you have any questions on how to use jsoup, or have ideas for future development, please get in touch via the [mailing list](https://jsoup.org/discussion). + +If you find any issues, please file a [bug](https://jsoup.org/bugs) after checking for duplicates. + +The [colophon](https://jsoup.org/colophon) talks about the history of and tools used to build jsoup. + +## Status +jsoup is in general, stable release. From 16b2ab8e25e6bea39ae04f9d310331e411fb2e65 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 17 Jan 2017 13:51:48 -0800 Subject: [PATCH 083/774] Added eachText and eachAttr --- CHANGES | 4 ++ src/main/java/org/jsoup/select/Elements.java | 41 +++++++++++++++++- .../java/org/jsoup/select/ElementsTest.java | 42 ++++++++++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 146140396e..0df2614989 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog *** Release 1.10.3 [PENDING] + * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, + respectively. This makes it simpler to for example get a list of each URL on a page: + List urls = doc.select("a").eachAttr("abs:href""); + * Improved selector validation for :contains(...) with unbalanced quotes. diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index e9e851d1de..246c9436ae 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -65,7 +65,7 @@ public String attr(String attributeKey) { } /** - Checks if any of the matched elements have this attribute set. + Checks if any of the matched elements have this attribute defined. @param attributeKey attribute key @return true if any of the elements have the attribute; false if none do. */ @@ -77,6 +77,22 @@ public boolean hasAttr(String attributeKey) { return false; } + /** + * Get the attribute value for each of the matched elements. If an element does not have this attribute, no value is + * included in the result set for that element. + * @param attributeKey the attribute name to return values for. You can add the {@code abs:} prefix to the key to + * get absolute URLs from relative URLs, e.g.: {@code doc.select("a").eachAttr("abs:href")} . + * @return a list of each element's attribute value for the attribute + */ + public List eachAttr(String attributeKey) { + List attrs = new ArrayList(size()); + for (Element element : this) { + if (element.hasAttr(attributeKey)) + attrs.add(element.attr(attributeKey)); + } + return attrs; + } + /** * Set an attribute on all matched elements. * @param attributeKey attribute key @@ -181,6 +197,7 @@ public Elements val(String value) { * children, as the Element.text() method returns the combined text of a parent and all its children. * @return string of all text: unescaped and no HTML. * @see Element#text() + * @see #eachText() */ public String text() { StringBuilder sb = new StringBuilder(); @@ -192,6 +209,11 @@ public String text() { return sb.toString(); } + /** + Test if any matched Element has any text content, that is not just whitespace. + @return true if any element has non-blank text content. + @see Element#hasText() + */ public boolean hasText() { for (Element element: this) { if (element.hasText()) @@ -199,6 +221,23 @@ public boolean hasText() { } return false; } + + /** + * Get the text content of each of the matched elements. If an element has no text, then it is not included in the + * result. + * @return A list of each matched element's text content. + * @see Element#text() + * @see Element#hasText() + * @see #text() + */ + public List eachText() { + ArrayList texts = new ArrayList(size()); + for (Element el: this) { + if (el.hasText()) + texts.add(el.text()); + } + return texts; + } /** * Get the combined inner HTML of all matched elements. diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index 79ca967ef7..771d33c8d3 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -9,7 +9,9 @@ import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** Tests for ElementList. @@ -328,4 +330,42 @@ public void tail(Node node, int depth) { assertEquals(1, prevAF.size()); assertEquals("1", prevAF.first().text()); } + + @Test public void eachText() { + Document doc = Jsoup.parse("

1

2

3

4

5

6

7

8

9

10

11

12

"); + List divText = doc.select("div").eachText(); + assertEquals(2, divText.size()); + assertEquals("1 2 3 4 5 6", divText.get(0)); + assertEquals("7 8 9 10 11 12", divText.get(1)); + + List pText = doc.select("p").eachText(); + Elements ps = doc.select("p"); + assertEquals(13, ps.size()); + assertEquals(12, pText.size()); // not 13, as last doesn't have text + assertEquals("1", pText.get(0)); + assertEquals("2", pText.get(1)); + assertEquals("5", pText.get(4)); + assertEquals("7", pText.get(6)); + assertEquals("12", pText.get(11)); + } + + @Test public void eachAttr() { + Document doc = Jsoup.parse( + "
1234", + "http://example.com"); + + List hrefAttrs = doc.select("a").eachAttr("href"); + assertEquals(3, hrefAttrs.size()); + assertEquals("/foo", hrefAttrs.get(0)); + assertEquals("http://example.com/bar", hrefAttrs.get(1)); + assertEquals("", hrefAttrs.get(2)); + assertEquals(4, doc.select("a").size()); + + List absAttrs = doc.select("a").eachAttr("abs:href"); + assertEquals(3, absAttrs.size()); + assertEquals(3, absAttrs.size()); + assertEquals("http://example.com/foo", absAttrs.get(0)); + assertEquals("http://example.com/bar", absAttrs.get(1)); + assertEquals("http://example.com", absAttrs.get(2)); + } } From b6dda00bc19f6d3bc4009a6b9a4a932de9640bd4 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Wed, 18 Jan 2017 13:35:52 -0800 Subject: [PATCH 084/774] No longer experimental. --- src/main/java/org/jsoup/helper/W3CDom.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 497e3ea129..d62eb9dfc7 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -23,9 +23,6 @@ /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, * for integration with toolsets that use the W3C DOM. - *

- * This class is currently experimental, please provide feedback on utility and any problems experienced. - *

*/ public class W3CDom { protected DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); From 83f01fd864e59fa69c894da06f4a15489222d401 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Jan 2017 15:26:56 -0800 Subject: [PATCH 085/774] Access the class attribute case-insensitiviely Fixes #814 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Attributes.java | 4 ++++ src/main/java/org/jsoup/nodes/Element.java | 2 +- .../java/org/jsoup/select/ElementsTest.java | 17 +++++++++++++++++ .../java/org/jsoup/select/SelectorTest.java | 10 ++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 0df2614989..d8be281bd2 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog exception. + * Bugfix: Element.hasClass() and the ".classname" selector would not find the class attribute case-insensitively. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 8fdb654d51..61844eeaf1 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -60,6 +60,10 @@ public String getIgnoreCase(String key) { if (attributes == null) return ""; + Attribute attr = attributes.get(key); + if (attr != null) + return attr.getValue(); + for (String attrKey : attributes.keySet()) { if (attrKey.equalsIgnoreCase(key)) return attributes.get(attrKey).getValue(); diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index caf111072c..175654eb0f 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1089,7 +1089,7 @@ public Element classNames(Set classNames) { */ // performance sensitive public boolean hasClass(String className) { - final String classAttr = attributes.get("class"); + final String classAttr = attributes.getIgnoreCase("class"); final int len = classAttr.length(); final int wantLen = className.length(); diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index 771d33c8d3..6fbec69a3a 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -3,6 +3,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.jsoup.nodes.FormElement; import org.jsoup.nodes.Node; import org.junit.Test; @@ -93,6 +94,22 @@ public class ElementsTest { assertEquals("blue", els.get(0).className()); assertEquals("red green blue mellow", els.get(1).className()); } + + @Test public void hasClassCaseInsensitive() { + Elements els = Jsoup.parse("

One

Two

THREE").select("p"); + Element one = els.get(0); + Element two = els.get(1); + Element thr = els.get(2); + + assertTrue(one.hasClass("One")); + assertTrue(one.hasClass("ONE")); + + assertTrue(two.hasClass("TWO")); + assertTrue(two.hasClass("Two")); + + assertTrue(thr.hasClass("ThreE")); + assertTrue(thr.hasClass("three")); + } @Test public void text() { String h = "

Hello

there

world

"; diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index c04a42a54b..3666de7082 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -48,6 +48,16 @@ public class SelectorTest { assertEquals(1, els2.size()); } + @Test public void testByClassCaseInsensitive() { + String html = "

One

Two

Three

Four"; + Elements elsFromClass = Jsoup.parse(html).select("P.Foo"); + Elements elsFromAttr = Jsoup.parse(html).select("p[class=foo]"); + + assertEquals(elsFromAttr.size(), elsFromClass.size()); + assertEquals(3, elsFromClass.size()); + assertEquals("Two", elsFromClass.get(1).text()); + } + @Test public void testByAttribute() { String h = "

" + "
"; From 56a728d482ce0c88cd9513eca1e7c7585f0718f9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Jan 2017 16:27:06 -0800 Subject: [PATCH 086/774] Fix double escaping of query string in redirect --- CHANGES | 3 +++ .../java/org/jsoup/helper/HttpConnection.java | 2 +- .../org/jsoup/integration/UrlConnectTest.java | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d8be281bd2..cc83d27e85 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,9 @@ jsoup changelog * Bugfix: Element.hasClass() and the ".classname" selector would not find the class attribute case-insensitively. + * Bugfix: In Jsoup.Connection, if a redirect contained a query string with %xx escapes, they would be double escaped + before the redirect was followed, leading to fetching an incorrect location. + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 87fd9aaf8e..f575c48174 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -67,7 +67,7 @@ private static String encodeUrl(String url) { private static URL encodeUrl(URL u) { try { // odd way to encode urls, but it works! - final URI uri = new URI(u.getProtocol(), u.getUserInfo(), u.getHost(), u.getPort(), u.getPath(), u.getQuery(), u.getRef()); + final URI uri = new URI(u.toExternalForm()); return new URL(uri.toASCIIString()); } catch (Exception e) { return u; diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 041e34f5ac..4dc7beffd5 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -796,4 +796,21 @@ public void run() { assertTrue(body[0].length() > 0); } + + @Test public void handlesEscapedRedirectUrls() throws IOException { + String url = "http://www.altalex.com/documents/news/2016/12/06/questioni-civilistiche-conseguenti-alla-depenalizzazione"; + // sends: Location:http://shop.wki.it/shared/sso/sso.aspx?sso=&url=http%3a%2f%2fwww.altalex.com%2fsession%2fset%2f%3freturnurl%3dhttp%253a%252f%252fwww.altalex.com%253a80%252fdocuments%252fnews%252f2016%252f12%252f06%252fquestioni-civilistiche-conseguenti-alla-depenalizzazione + // then to: http://www.altalex.com/session/set/?returnurl=http%3a%2f%2fwww.altalex.com%3a80%2fdocuments%2fnews%2f2016%2f12%2f06%2fquestioni-civilistiche-conseguenti-alla-depenalizzazione&sso=RDRG6T684G4AK2E7U591UGR923 + // then : http://www.altalex.com:80/documents/news/2016/12/06/questioni-civilistiche-conseguenti-alla-depenalizzazione + + // bug is that jsoup goes to + // GET /shared/sso/sso.aspx?sso=&url=http%253a%252f%252fwww.altalex.com%252fsession%252fset%252f%253freturnurl%253dhttp%25253a%25252f%25252fwww.altalex.com%25253a80%25252fdocuments%25252fnews%25252f2016%25252f12%25252f06%25252fquestioni-civilistiche-conseguenti-alla-depenalizzazione HTTP/1.1 + // i.e. double escaped + + Connection.Response res = Jsoup.connect(url) + .proxy("localhost", 8888) + .execute(); + Document doc = res.parse(); + assertEquals(200, res.statusCode()); + } } From f28c024ba127fd701f0d195a359afbabff04d7a1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 23 Jan 2017 11:46:21 -0800 Subject: [PATCH 087/774] Readme location --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2cba88001b..184aaa729d 100644 --- a/pom.xml +++ b/pom.xml @@ -150,7 +150,7 @@ false LICENSE - README + README.md CHANGES From 57855251cf0d6ea42ac57efe361531d34720d151 Mon Sep 17 00:00:00 2001 From: Lex Spoon Date: Tue, 24 Jan 2017 19:23:43 -0500 Subject: [PATCH 088/774] Minor fix for the parsing of non-empty selects --- src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 3 ++- src/test/java/org/jsoup/parser/HtmlParserTest.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index c515462c77..9f362fd436 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1229,7 +1229,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (name.equals("html")) return tb.process(start, InBody); else if (name.equals("option")) { - tb.processEndTag("option"); + if (tb.currentElement().nodeName().equals("option")) + tb.processEndTag("option"); tb.insert(start); } else if (name.equals("optgroup")) { if (tb.currentElement().nodeName().equals("option")) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 4ab935918c..d1717fbba7 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -112,6 +112,13 @@ public class HtmlParserTest { assertEquals("TwoThree", options.last().text()); } + @Test public void testSelectWithOption() { + Parser parser = Parser.htmlParser(); + parser.setTrackErrors(10); + Document document = parser.parseInput("", "http://jsoup.org"); + assertEquals(0, parser.getErrors().size()); + } + @Test public void testSpaceAfterTag() { Document doc = Jsoup.parse("

Hello

"); assertEquals("

Hello

", TextUtil.stripNewlines(doc.body().html())); From 7462c0d9be34f6546520cf567dd5ea402c9cf329 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 30 Jan 2017 21:47:20 +0100 Subject: [PATCH 089/774] Add tests to check for locale-independent lower-casing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the default locale is set to Turkish, "I".toLowerCase() returns "ı", the dotless I. The method toLowerCase() is used throughout the code to normalize values. But none of these should be locale-sensitive. That's why right now all the added tests are failing. --- src/test/java/org/jsoup/MultiLocaleRule.java | 41 ++++++++++++ .../org/jsoup/helper/HttpConnectionTest.java | 14 +++- .../org/jsoup/parser/ParserSettingsTest.java | 43 ++++++++++--- src/test/java/org/jsoup/parser/TagTest.java | 13 ++-- .../java/org/jsoup/safety/CleanerTest.java | 19 +++++- .../java/org/jsoup/select/SelectorTest.java | 64 ++++++++++++------- 6 files changed, 152 insertions(+), 42 deletions(-) create mode 100644 src/test/java/org/jsoup/MultiLocaleRule.java diff --git a/src/test/java/org/jsoup/MultiLocaleRule.java b/src/test/java/org/jsoup/MultiLocaleRule.java new file mode 100644 index 0000000000..3a0ee98bef --- /dev/null +++ b/src/test/java/org/jsoup/MultiLocaleRule.java @@ -0,0 +1,41 @@ +package org.jsoup; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Locale; + +public class MultiLocaleRule implements TestRule { + @Retention(RetentionPolicy.RUNTIME) + public @interface MultiLocaleTest { + } + + public Statement apply(final Statement statement, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + MultiLocaleTest annotation = description.getAnnotation(MultiLocaleTest.class); + if (annotation == null) { + statement.evaluate(); + return; + } + + evaluateWithLocale(Locale.ENGLISH); + evaluateWithLocale(new Locale("tr")); + } + + private void evaluateWithLocale(Locale locale) throws Throwable { + Locale oldLocale = Locale.getDefault(); + Locale.setDefault(locale); + try { + statement.evaluate(); + } finally { + Locale.setDefault(oldLocale); + } + } + }; + } +} diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 09e52e2ede..5e5cab33b0 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -2,7 +2,10 @@ import static org.junit.Assert.*; +import org.jsoup.MultiLocaleRule; +import org.jsoup.MultiLocaleRule.MultiLocaleTest; import org.jsoup.integration.ParseTest; +import org.junit.Rule; import org.junit.Test; import org.jsoup.Connection; @@ -14,6 +17,8 @@ public class HttpConnectionTest { /* most actual network http connection tests are in integration */ + @Rule public MultiLocaleRule rule = new MultiLocaleRule(); + @Test(expected=IllegalArgumentException.class) public void throwsExceptionOnParseWithoutExecute() throws IOException { Connection con = HttpConnection.connect("http://example.com"); con.response().parse(); @@ -29,7 +34,7 @@ public class HttpConnectionTest { con.response().bodyAsBytes(); } - @Test public void caseInsensitiveHeaders() { + @Test @MultiLocaleTest public void caseInsensitiveHeaders() { Connection.Response res = new HttpConnection.Response(); Map headers = res.headers(); headers.put("Accept-Encoding", "gzip"); @@ -39,15 +44,20 @@ public class HttpConnectionTest { assertTrue(res.hasHeader("Accept-Encoding")); assertTrue(res.hasHeader("accept-encoding")); assertTrue(res.hasHeader("accept-Encoding")); + assertTrue(res.hasHeader("ACCEPT-ENCODING")); assertEquals("gzip", res.header("accept-Encoding")); + assertEquals("gzip", res.header("ACCEPT-ENCODING")); assertEquals("text/html", res.header("Content-Type")); assertEquals("http://example.com", res.header("Referrer")); res.removeHeader("Content-Type"); assertFalse(res.hasHeader("content-type")); - res.header("accept-encoding", "deflate"); + res.removeHeader("ACCEPT-ENCODING"); + assertFalse(res.hasHeader("Accept-Encoding")); + + res.header("ACCEPT-ENCODING", "deflate"); assertEquals("deflate", res.header("Accept-Encoding")); assertEquals("deflate", res.header("accept-Encoding")); } diff --git a/src/test/java/org/jsoup/parser/ParserSettingsTest.java b/src/test/java/org/jsoup/parser/ParserSettingsTest.java index ee35091bbf..b1bcd13bbd 100644 --- a/src/test/java/org/jsoup/parser/ParserSettingsTest.java +++ b/src/test/java/org/jsoup/parser/ParserSettingsTest.java @@ -1,27 +1,50 @@ package org.jsoup.parser; +import org.jsoup.MultiLocaleRule; +import org.jsoup.MultiLocaleRule.MultiLocaleTest; +import org.jsoup.nodes.Attributes; +import org.junit.Rule; import org.junit.Test; + import static org.junit.Assert.assertEquals; public class ParserSettingsTest { - @Test - public void caseSupport() { + @Rule public MultiLocaleRule rule = new MultiLocaleRule(); + + @Test @MultiLocaleTest public void caseSupport() { ParseSettings bothOn = new ParseSettings(true, true); ParseSettings bothOff = new ParseSettings(false, false); ParseSettings tagOn = new ParseSettings(true, false); ParseSettings attrOn = new ParseSettings(false, true); - assertEquals("FOO", bothOn.normalizeTag("FOO")); - assertEquals("FOO", bothOn.normalizeAttribute("FOO")); + assertEquals("IMG", bothOn.normalizeTag("IMG")); + assertEquals("ID", bothOn.normalizeAttribute("ID")); + + assertEquals("img", bothOff.normalizeTag("IMG")); + assertEquals("id", bothOff.normalizeAttribute("ID")); - assertEquals("foo", bothOff.normalizeTag("FOO")); - assertEquals("foo", bothOff.normalizeAttribute("FOO")); + assertEquals("IMG", tagOn.normalizeTag("IMG")); + assertEquals("id", tagOn.normalizeAttribute("ID")); + + assertEquals("img", attrOn.normalizeTag("IMG")); + assertEquals("ID", attrOn.normalizeAttribute("ID")); + } + + @Test @MultiLocaleTest public void attributeCaseNormalization() throws Exception { + ParseSettings parseSettings = new ParseSettings(false, false); + + String normalizedAttribute = parseSettings.normalizeAttribute("HIDDEN"); + + assertEquals("hidden", normalizedAttribute); + } - assertEquals("FOO", tagOn.normalizeTag("FOO")); - assertEquals("foo", tagOn.normalizeAttribute("FOO")); + @Test @MultiLocaleTest public void attributesCaseNormalization() throws Exception { + ParseSettings parseSettings = new ParseSettings(false, false); + Attributes attributes = new Attributes(); + attributes.put("ITEM", "1"); - assertEquals("foo", attrOn.normalizeTag("FOO")); - assertEquals("FOO", attrOn.normalizeAttribute("FOO")); + Attributes normalizedAttributes = parseSettings.normalizeAttributes(attributes); + assertEquals("item", normalizedAttributes.asList().get(0).getKey()); } } diff --git a/src/test/java/org/jsoup/parser/TagTest.java b/src/test/java/org/jsoup/parser/TagTest.java index da6195fb76..507a08e045 100644 --- a/src/test/java/org/jsoup/parser/TagTest.java +++ b/src/test/java/org/jsoup/parser/TagTest.java @@ -1,12 +1,17 @@ package org.jsoup.parser; +import org.jsoup.MultiLocaleRule; +import org.jsoup.MultiLocaleRule.MultiLocaleTest; +import org.junit.Rule; import org.junit.Test; + import static org.junit.Assert.*; /** Tag tests. @author Jonathan Hedley, jonathan@hedley.net */ public class TagTest { + @Rule public MultiLocaleRule rule = new MultiLocaleRule(); @Test public void isCaseSensitive() { Tag p1 = Tag.valueOf("P"); @@ -14,10 +19,10 @@ public class TagTest { assertFalse(p1.equals(p2)); } - @Test public void canBeInsensitive() { - Tag p1 = Tag.valueOf("P", ParseSettings.htmlDefault); - Tag p2 = Tag.valueOf("p", ParseSettings.htmlDefault); - assertEquals(p1, p2); + @Test @MultiLocaleTest public void canBeInsensitive() { + Tag script1 = Tag.valueOf("script", ParseSettings.htmlDefault); + Tag script2 = Tag.valueOf("SCRIPT", ParseSettings.htmlDefault); + assertSame(script1, script2); } @Test public void trims() { diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 9bc78e65f7..50876d0539 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -1,10 +1,12 @@ package org.jsoup.safety; +import org.jsoup.MultiLocaleRule; +import org.jsoup.MultiLocaleRule.MultiLocaleTest; import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; import org.jsoup.nodes.Entities; +import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; @@ -14,6 +16,8 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class CleanerTest { + @Rule public MultiLocaleRule rule = new MultiLocaleRule(); + @Test public void simpleBehaviourTest() { String h = ""; String cleanHtml = Jsoup.clean(h, Whitelist.simpleText()); @@ -77,7 +81,18 @@ public class CleanerTest { assertEquals("

Contact me here

", TextUtil.stripNewlines(cleanHtml)); } - + + @Test @MultiLocaleTest public void whitelistedProtocolShouldBeRetained() { + Whitelist whitelist = Whitelist.none() + .addTags("a") + .addAttributes("a", "href") + .addProtocols("a", "href", "something"); + + String cleanHtml = Jsoup.clean("", whitelist); + + assertEquals("", TextUtil.stripNewlines(cleanHtml)); + } + @Test public void testDropComments() { String h = "

Hello

"; String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 3666de7082..6126406b62 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1,8 +1,11 @@ package org.jsoup.select; +import org.jsoup.MultiLocaleRule; +import org.jsoup.MultiLocaleRule.MultiLocaleTest; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; @@ -13,6 +16,8 @@ * @author Jonathan Hedley, jonathan@hedley.net */ public class SelectorTest { + @Rule public MultiLocaleRule rule = new MultiLocaleRule(); + @Test public void testByTag() { // should be case insensitive Elements els = Jsoup.parse("

Hello

").select("DIV"); @@ -58,8 +63,8 @@ public class SelectorTest { assertEquals("Two", elsFromClass.get(1).text()); } - @Test public void testByAttribute() { - String h = "
" + + @Test @MultiLocaleTest public void testByAttribute() { + String h = "
" + "
"; Document doc = Jsoup.parse(h); @@ -86,17 +91,17 @@ public class SelectorTest { Elements starts = doc.select("[title^=ba]"); assertEquals(2, starts.size()); assertEquals("Bar", starts.first().attr("title")); - assertEquals("Bam", starts.last().attr("title")); + assertEquals("Balim", starts.last().attr("title")); - Elements ends = doc.select("[title$=am]"); + Elements ends = doc.select("[title$=im]"); assertEquals(2, ends.size()); - assertEquals("Bam", ends.first().attr("title")); - assertEquals("SLAM", ends.last().attr("title")); + assertEquals("Balim", ends.first().attr("title")); + assertEquals("SLIM", ends.last().attr("title")); - Elements contains = doc.select("[title*=a]"); - assertEquals(3, contains.size()); - assertEquals("Bar", contains.first().attr("title")); - assertEquals("SLAM", contains.last().attr("title")); + Elements contains = doc.select("[title*=i]"); + assertEquals(2, contains.size()); + assertEquals("Balim", contains.first().attr("title")); + assertEquals("SLIM", contains.last().attr("title")); } @Test public void testNamespacedTag() { @@ -141,8 +146,8 @@ public class SelectorTest { assertEquals("2", byContains.last().id()); } - @Test public void testByAttributeStarting() { - Document doc = Jsoup.parse("
Hello

There

No

"); + @Test @MultiLocaleTest public void testByAttributeStarting() { + Document doc = Jsoup.parse("
Hello

There

No

"); Elements withData = doc.select("[^data-]"); assertEquals(2, withData.size()); assertEquals("1", withData.first().id()); @@ -151,6 +156,8 @@ public class SelectorTest { withData = doc.select("p[^data-]"); assertEquals(1, withData.size()); assertEquals("2", withData.first().id()); + + assertEquals(1, doc.select("[^attrib]").size()); } @Test public void testByAttributeRegex() { @@ -518,8 +525,8 @@ public class SelectorTest { assertEquals("Two", divs.first().text()); } - @Test public void testPseudoContains() { - Document doc = Jsoup.parse("

The Rain.

The rain.

Rain, the.

"); + @Test @MultiLocaleTest public void testPseudoContains() { + Document doc = Jsoup.parse("

The Rain.

The RAIN.

Rain, the.

"); Elements ps1 = doc.select("p:contains(Rain)"); assertEquals(3, ps1.size()); @@ -527,7 +534,7 @@ public class SelectorTest { Elements ps2 = doc.select("p:contains(the rain)"); assertEquals(2, ps2.size()); assertEquals("The Rain.", ps2.first().html()); - assertEquals("The rain.", ps2.last().html()); + assertEquals("The RAIN.", ps2.last().html()); Elements ps3 = doc.select("p:contains(the Rain):has(i)"); assertEquals(1, ps3.size()); @@ -539,6 +546,9 @@ public class SelectorTest { Elements ps5 = doc.select(":contains(rain)"); assertEquals(8, ps5.size()); // html, body, div,... + + Elements ps6 = doc.select(":contains(RAIN)"); + assertEquals(8, ps6.size()); } @Test public void testPsuedoContainsWithParentheses() { @@ -553,13 +563,17 @@ public class SelectorTest { assertEquals("2", ps2.first().id()); } - @Test public void containsOwn() { - Document doc = Jsoup.parse("

Hello there now

"); - Elements ps = doc.select("p:containsOwn(Hello now)"); + @Test @MultiLocaleTest public void containsOwn() { + Document doc = Jsoup.parse("

Hello there igor

"); + Elements ps = doc.select("p:containsOwn(Hello IGOR)"); assertEquals(1, ps.size()); assertEquals("1", ps.first().id()); assertEquals(0, doc.select("p:containsOwn(there)").size()); + + Document doc2 = Jsoup.parse("

Hello there IGOR

"); + assertEquals(1, doc2.select("p:containsOwn(igor)").size()); + } @Test public void testMatches() { @@ -703,26 +717,28 @@ public void selectClassWithSpace() { assertEquals("Two", doc.select("div[data=\"[Another)]]\"]").first().text()); } - @Test public void containsData() { - String html = "

jsoup

"; + @Test @MultiLocaleTest public void containsData() { + String html = "

function

"; Document doc = Jsoup.parse(html); Element body = doc.body(); - Elements dataEls1 = body.select(":containsData(jsoup)"); - Elements dataEls2 = body.select("script:containsData(jsoup)"); + Elements dataEls1 = body.select(":containsData(function)"); + Elements dataEls2 = body.select("script:containsData(function)"); Elements dataEls3 = body.select("span:containsData(comments)"); - Elements dataEls4 = body.select(":containsData(s)"); + Elements dataEls4 = body.select(":containsData(o)"); + Elements dataEls5 = body.select("style:containsData(ITEM)"); assertEquals(2, dataEls1.size()); // body and script assertEquals(1, dataEls2.size()); assertEquals(dataEls1.last(), dataEls2.first()); - assertEquals("", dataEls2.outerHtml()); + assertEquals("", dataEls2.outerHtml()); assertEquals(1, dataEls3.size()); assertEquals("span", dataEls3.first().tagName()); assertEquals(3, dataEls4.size()); assertEquals("body", dataEls4.first().tagName()); assertEquals("script", dataEls4.get(1).tagName()); assertEquals("span", dataEls4.get(2).tagName()); + assertEquals(1, dataEls5.size()); } @Test public void containsWithQuote() { From 8121d8cfd151ae981f1ce6e21203f2b8c24fcf31 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 30 Jan 2017 23:34:38 +0100 Subject: [PATCH 090/774] Always use Locale.ENGLISH when converting to lower case Fixes #256 --- .../java/org/jsoup/helper/HttpConnection.java | 7 +++-- .../java/org/jsoup/internal/Normalizer.java | 13 ++++++++ src/main/java/org/jsoup/nodes/Element.java | 4 ++- src/main/java/org/jsoup/nodes/Node.java | 4 ++- .../java/org/jsoup/parser/ParseSettings.java | 8 +++-- src/main/java/org/jsoup/parser/Token.java | 8 +++-- src/main/java/org/jsoup/safety/Whitelist.java | 4 ++- src/main/java/org/jsoup/select/Evaluator.java | 31 ++++++++++--------- .../java/org/jsoup/select/QueryParser.java | 6 ++-- 9 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/jsoup/internal/Normalizer.java diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index f575c48174..fff2c27024 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -19,6 +19,7 @@ import java.util.zip.GZIPInputStream; import static org.jsoup.Connection.Method.HEAD; +import static org.jsoup.internal.Normalizer.lowerCase; /** * Implementation of {@link Connection}. @@ -412,7 +413,7 @@ private String getHeaderCaseInsensitive(String name) { // quick evals for common case of title case, lower case, then scan for mixed String value = headers.get(name); if (value == null) - value = headers.get(name.toLowerCase()); + value = headers.get(lowerCase(name)); if (value == null) { Map.Entry entry = scanHeaders(name); if (entry != null) @@ -422,9 +423,9 @@ private String getHeaderCaseInsensitive(String name) { } private Map.Entry scanHeaders(String name) { - String lc = name.toLowerCase(); + String lc = lowerCase(name); for (Map.Entry entry : headers.entrySet()) { - if (entry.getKey().toLowerCase().equals(lc)) + if (lowerCase(entry.getKey()).equals(lc)) return entry; } return null; diff --git a/src/main/java/org/jsoup/internal/Normalizer.java b/src/main/java/org/jsoup/internal/Normalizer.java new file mode 100644 index 0000000000..02c31584d5 --- /dev/null +++ b/src/main/java/org/jsoup/internal/Normalizer.java @@ -0,0 +1,13 @@ +package org.jsoup.internal; + +import java.util.Locale; + +public class Normalizer { + public static String lowerCase(String input) { + return input.toLowerCase(Locale.ENGLISH); + } + + public static String normalize(String input) { + return lowerCase(input).trim(); + } +} diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 175654eb0f..a5f36e83c2 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -25,6 +25,8 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import static org.jsoup.internal.Normalizer.normalize; + /** * A HTML element consists of a tag name, attributes, and child nodes (including text nodes and * other elements). @@ -647,7 +649,7 @@ private static Integer indexInList(Element search, List e */ public Elements getElementsByTag(String tagName) { Validate.notEmpty(tagName); - tagName = tagName.toLowerCase().trim(); + tagName = normalize(tagName); return Collector.collect(new Evaluator.Tag(tagName), this); } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index ed19156538..947cfe083f 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -13,6 +13,8 @@ import java.util.LinkedList; import java.util.List; +import static org.jsoup.internal.Normalizer.lowerCase; + /** The base, abstract Node model. Elements, Documents, Comments etc are all Node instances. @@ -78,7 +80,7 @@ public String attr(String attributeKey) { String val = attributes.getIgnoreCase(attributeKey); if (val.length() > 0) return val; - else if (attributeKey.toLowerCase().startsWith("abs:")) + else if (lowerCase(attributeKey).startsWith("abs:")) return absUrl(attributeKey.substring("abs:".length())); else return ""; } diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index 9ac7e6b1f9..e9df9dffd8 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -3,6 +3,8 @@ import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; +import static org.jsoup.internal.Normalizer.lowerCase; + /** * Controls parser settings, to optionally preserve tag and/or attribute name case. */ @@ -37,21 +39,21 @@ public ParseSettings(boolean tag, boolean attribute) { String normalizeTag(String name) { name = name.trim(); if (!preserveTagCase) - name = name.toLowerCase(); + name = lowerCase(name); return name; } String normalizeAttribute(String name) { name = name.trim(); if (!preserveAttributeCase) - name = name.toLowerCase(); + name = lowerCase(name); return name; } Attributes normalizeAttributes(Attributes attributes) { if (!preserveAttributeCase) { for (Attribute attr : attributes) { - attr.setKey(attr.getKey().toLowerCase()); + attr.setKey(lowerCase(attr.getKey())); } } return attributes; diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index af51432173..c983557e82 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -5,6 +5,8 @@ import org.jsoup.nodes.Attributes; import org.jsoup.nodes.BooleanAttribute; +import static org.jsoup.internal.Normalizer.lowerCase; + /** * Parse tokens for the Tokeniser. */ @@ -142,7 +144,7 @@ final String normalName() { // loses case, used in tree building for working out final Tag name(String name) { tagName = name; - normalName = name.toLowerCase(); + normalName = lowerCase(name); return this; } @@ -158,7 +160,7 @@ final Attributes getAttributes() { // these appenders are rarely hit in not null state-- caused by null chars. final void appendTagName(String append) { tagName = tagName == null ? append : tagName.concat(append); - normalName = tagName.toLowerCase(); + normalName = lowerCase(tagName); } final void appendTagName(char append) { @@ -231,7 +233,7 @@ Tag reset() { StartTag nameAttr(String name, Attributes attributes) { this.tagName = name; this.attributes = attributes; - normalName = tagName.toLowerCase(); + normalName = lowerCase(tagName); return this; } diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index b8d0886008..8258ce9528 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -15,6 +15,8 @@ Thank you to Ryan Grove (wonko.com) for the Ruby HTML cleaner http://github.com/ import java.util.Map; import java.util.Set; +import static org.jsoup.internal.Normalizer.lowerCase; + /** Whitelists define what HTML (elements and attributes) to allow through the cleaner. Everything else is removed. @@ -542,7 +544,7 @@ private boolean testValidProtocol(Element el, Attribute attr, Set prot prot += ":"; - if (value.toLowerCase().startsWith(prot)) { + if (lowerCase(value).startsWith(prot)) { return true; } } diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index d363f16247..e797495eaf 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -12,6 +12,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.jsoup.internal.Normalizer.lowerCase; +import static org.jsoup.internal.Normalizer.normalize; + /** * Evaluates that an element matches the selector. @@ -147,14 +150,14 @@ public static final class AttributeStarting extends Evaluator { public AttributeStarting(String keyPrefix) { Validate.notEmpty(keyPrefix); - this.keyPrefix = keyPrefix.toLowerCase(); + this.keyPrefix = lowerCase(keyPrefix); } @Override public boolean matches(Element root, Element element) { List values = element.attributes().asList(); for (org.jsoup.nodes.Attribute attribute : values) { - if (attribute.getKey().toLowerCase().startsWith(keyPrefix)) + if (lowerCase(attribute.getKey()).startsWith(keyPrefix)) return true; } return false; @@ -217,7 +220,7 @@ public AttributeWithValueStarting(String key, String value) { @Override public boolean matches(Element root, Element element) { - return element.hasAttr(key) && element.attr(key).toLowerCase().startsWith(value); // value is lower case already + return element.hasAttr(key) && lowerCase(element.attr(key)).startsWith(value); // value is lower case already } @Override @@ -237,7 +240,7 @@ public AttributeWithValueEnding(String key, String value) { @Override public boolean matches(Element root, Element element) { - return element.hasAttr(key) && element.attr(key).toLowerCase().endsWith(value); // value is lower case + return element.hasAttr(key) && lowerCase(element.attr(key)).endsWith(value); // value is lower case } @Override @@ -257,7 +260,7 @@ public AttributeWithValueContaining(String key, String value) { @Override public boolean matches(Element root, Element element) { - return element.hasAttr(key) && element.attr(key).toLowerCase().contains(value); // value is lower case + return element.hasAttr(key) && lowerCase(element.attr(key)).contains(value); // value is lower case } @Override @@ -275,7 +278,7 @@ public static final class AttributeWithValueMatching extends Evaluator { Pattern pattern; public AttributeWithValueMatching(String key, Pattern pattern) { - this.key = key.trim().toLowerCase(); + this.key = normalize(key); this.pattern = pattern; } @@ -302,12 +305,12 @@ public AttributeKeyPair(String key, String value) { Validate.notEmpty(key); Validate.notEmpty(value); - this.key = key.trim().toLowerCase(); + this.key = normalize(key); if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) { value = value.substring(1, value.length()-1); } - this.value = value.trim().toLowerCase(); + this.value = normalize(value); } } @@ -648,12 +651,12 @@ public static final class ContainsText extends Evaluator { private String searchText; public ContainsText(String searchText) { - this.searchText = searchText.toLowerCase(); + this.searchText = lowerCase(searchText); } @Override public boolean matches(Element root, Element element) { - return (element.text().toLowerCase().contains(searchText)); + return lowerCase(element.text()).contains(searchText); } @Override @@ -669,12 +672,12 @@ public static final class ContainsData extends Evaluator { private String searchText; public ContainsData(String searchText) { - this.searchText = searchText.toLowerCase(); + this.searchText = lowerCase(searchText); } @Override public boolean matches(Element root, Element element) { - return (element.data().toLowerCase().contains(searchText)); + return lowerCase(element.data()).contains(searchText); } @Override @@ -690,12 +693,12 @@ public static final class ContainsOwnText extends Evaluator { private String searchText; public ContainsOwnText(String searchText) { - this.searchText = searchText.toLowerCase(); + this.searchText = lowerCase(searchText); } @Override public boolean matches(Element root, Element element) { - return (element.ownText().toLowerCase().contains(searchText)); + return lowerCase(element.ownText()).contains(searchText); } @Override diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index d3a20418da..46c6c2dd41 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -9,6 +9,8 @@ import org.jsoup.helper.Validate; import org.jsoup.parser.TokenQueue; +import static org.jsoup.internal.Normalizer.normalize; + /** * Parses a CSS selector into an Evaluator tree. */ @@ -222,7 +224,7 @@ private void byTag() { // namespaces: wildcard match equals(tagName) or ending in ":"+tagName if (tagName.startsWith("*|")) { - evals.add(new CombiningEvaluator.Or(new Evaluator.Tag(tagName.trim().toLowerCase()), new Evaluator.TagEndsWith(tagName.replace("*|", ":").trim().toLowerCase()))); + evals.add(new CombiningEvaluator.Or(new Evaluator.Tag(normalize(tagName)), new Evaluator.TagEndsWith(normalize(tagName.replace("*|", ":"))))); } else { // namespaces: if element name is "abc:def", selector must be "abc|def", so flip: if (tagName.contains("|")) @@ -288,7 +290,7 @@ private void indexEquals() { private static final Pattern NTH_B = Pattern.compile("(\\+|-)?(\\d+)"); private void cssNthChild(boolean backwards, boolean ofType) { - String argS = tq.chompTo(")").trim().toLowerCase(); + String argS = normalize(tq.chompTo(")")); Matcher mAB = NTH_AB.matcher(argS); Matcher mB = NTH_B.matcher(argS); final int a, b; From 6dcbf7104240e6de0898f6d97e91ee8ebb68f0a2 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 22 Feb 2017 09:58:21 +0100 Subject: [PATCH 091/774] Add failing test for document with two meta elements with content type When attempting to retrieve the charset only the first meta element with content type information will be looked at. No further meta elements are considered even when the first one doesn't contain a charset parameter. --- src/test/java/org/jsoup/helper/DataUtilTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 5555ea1158..43125b20f8 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -101,6 +101,18 @@ public void wrongMetaCharsetFallback() { } } + @Test + public void secondMetaElementWithContentTypeContainsCharsetParameter() throws Exception { + ByteBuffer inBuffer = ByteBuffer.wrap(("" + + "" + + "" + + " 한국어").getBytes("euc-kr")); + + Document doc = DataUtil.parseByteData(inBuffer, null, "http://example.com", Parser.htmlParser()); + + assertEquals("한국어", doc.body().text()); + } + @Test public void supportsBOMinFiles() throws IOException { // test files from http://www.i18nl10n.com/korean/utftest/ From a5266b14a2b7457e076d5f6429c2197baf5412e4 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 22 Feb 2017 10:03:40 +0100 Subject: [PATCH 092/774] Consider all meta elements until a charset is found --- src/main/java/org/jsoup/helper/DataUtil.java | 9 +++++++-- src/test/java/org/jsoup/helper/DataUtilTest.java | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index b32d86af5d..44a4db39bc 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -4,6 +4,7 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.XmlDeclaration; import org.jsoup.parser.Parser; +import org.jsoup.select.Elements; import java.io.ByteArrayOutputStream; import java.io.File; @@ -101,16 +102,20 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba // look for or HTML5 docData = Charset.forName(defaultCharset).decode(byteData).toString(); doc = parser.parseInput(docData, baseUri); - Element meta = doc.select("meta[http-equiv=content-type], meta[charset]").first(); + Elements metaElements = doc.select("meta[http-equiv=content-type], meta[charset]"); String foundCharset = null; // if not found, will keep utf-8 as best attempt - if (meta != null) { + for (Element meta : metaElements) { if (meta.hasAttr("http-equiv")) { foundCharset = getCharsetFromContentType(meta.attr("content")); } if (foundCharset == null && meta.hasAttr("charset")) { foundCharset = meta.attr("charset"); } + if (foundCharset != null) { + break; + } } + // look for if (foundCharset == null && doc.childNodeSize() > 0 && doc.childNode(0) instanceof XmlDeclaration) { XmlDeclaration prolog = (XmlDeclaration) doc.childNode(0); diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 43125b20f8..94003b4912 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -113,6 +113,18 @@ public void secondMetaElementWithContentTypeContainsCharsetParameter() throws Ex assertEquals("한국어", doc.body().text()); } + @Test + public void firstMetaElementWithCharsetShouldBeUsedForDecoding() throws Exception { + ByteBuffer inBuffer = ByteBuffer.wrap(("" + + "" + + "" + + " Übergrößenträger").getBytes("iso-8859-1")); + + Document doc = DataUtil.parseByteData(inBuffer, null, "http://example.com", Parser.htmlParser()); + + assertEquals("Übergrößenträger", doc.body().text()); + } + @Test public void supportsBOMinFiles() throws IOException { // test files from http://www.i18nl10n.com/korean/utftest/ From 0db39c27585d52fc19084a8090face5249e2bb4c Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Wed, 10 May 2017 11:54:08 +0200 Subject: [PATCH 093/774] Fix #872 Set request body to null when doing a redirect --- src/main/java/org/jsoup/helper/HttpConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index f575c48174..adfbc02fbe 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -662,6 +662,7 @@ else if (methodHasBody) if (status != HTTP_TEMP_REDIR) { req.method(Method.GET); // always redirect with a get. any data param from original req are dropped. req.data().clear(); + req.requestBody(null); } String location = res.header(LOCATION); From 38049ce2808cafce3884c25c4613aaaff9f2d0d8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 12:25:30 -0700 Subject: [PATCH 094/774] Memoize the child element list, so that sibling index queries are much faster Measurement was 24 ops/s prior, now 450 ops/s. (When matching ~ 12K elements in a loop) --- CHANGES | 3 + .../helper/ChangeNotifyingArrayList.java | 82 +++++++++++++++++++ src/main/java/org/jsoup/nodes/Element.java | 71 ++++++++++++---- src/main/java/org/jsoup/nodes/Node.java | 20 ++++- .../java/org/jsoup/nodes/ElementTest.java | 45 +++++++++- 5 files changed, 202 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/jsoup/helper/ChangeNotifyingArrayList.java diff --git a/CHANGES b/CHANGES index cc83d27e85..703771003d 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ jsoup changelog * Improved selector validation for :contains(...) with unbalanced quotes. + * Improved the speed of index based CSS selectors and other methods that use elementSiblingIndex, by a factor of 18x. + + * Bugfix: if an attribute name started or ended with a control character, the parse would fail with a validation exception. diff --git a/src/main/java/org/jsoup/helper/ChangeNotifyingArrayList.java b/src/main/java/org/jsoup/helper/ChangeNotifyingArrayList.java new file mode 100644 index 0000000000..67ac78d3e3 --- /dev/null +++ b/src/main/java/org/jsoup/helper/ChangeNotifyingArrayList.java @@ -0,0 +1,82 @@ +package org.jsoup.helper; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Implementation of ArrayList that watches out for changes to the contents. + */ +public abstract class ChangeNotifyingArrayList extends ArrayList { + public ChangeNotifyingArrayList(int initialCapacity) { + super(initialCapacity); + } + + public abstract void onContentsChanged(); + + @Override + public E set(int index, E element) { + onContentsChanged(); + return super.set(index, element); + } + + @Override + public boolean add(E e) { + onContentsChanged(); + return super.add(e); + } + + @Override + public void add(int index, E element) { + onContentsChanged(); + super.add(index, element); + } + + @Override + public E remove(int index) { + onContentsChanged(); + return super.remove(index); + } + + @Override + public boolean remove(Object o) { + onContentsChanged(); + return super.remove(o); + } + + @Override + public void clear() { + onContentsChanged(); + super.clear(); + } + + @Override + public boolean addAll(Collection c) { + onContentsChanged(); + return super.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + onContentsChanged(); + return super.addAll(index, c); + } + + @Override + protected void removeRange(int fromIndex, int toIndex) { + onContentsChanged(); + super.removeRange(fromIndex, toIndex); + } + + @Override + public boolean removeAll(Collection c) { + onContentsChanged(); + return super.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + onContentsChanged(); + return super.retainAll(c); + } + +} diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 175654eb0f..5b0315bce6 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -14,6 +14,7 @@ import org.jsoup.select.Selector; import java.io.IOException; +import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -35,6 +36,7 @@ */ public class Element extends Node { private Tag tag; + private SoftReference> shadowChildrenRef; // points to child elements shadowed from node children private static final Pattern classSplit = Pattern.compile("\\s+"); @@ -207,7 +209,7 @@ private static void accumulateParents(Element el, Elements parents) { * @see #childNode(int) */ public Element child(int index) { - return children().get(index); + return childElementsList().get(index); } /** @@ -215,18 +217,38 @@ public Element child(int index) { *

* This is effectively a filter on {@link #childNodes()} to get Element nodes. *

- * @return child elements. If this element has no children, returns an - * empty list. + * @return child elements. If this element has no children, returns an empty list. * @see #childNodes() */ public Elements children() { - // create on the fly rather than maintaining two lists. if gets slow, memoize, and mark dirty on change - List elements = new ArrayList(childNodes.size()); - for (Node node : childNodes) { - if (node instanceof Element) - elements.add((Element) node); + return new Elements(childElementsList()); + } + + /** + * Maintains a shadow copy of this element's child elements. If the nodelist is changed, this cache is invalidated. + * TODO - think about pulling this out as a helper as there are other shadow lists (like in Attributes) kept around. + * @return a list of child elements + */ + private List childElementsList() { + List children; + if (shadowChildrenRef == null || (children = shadowChildrenRef.get()) == null) { + children = new ArrayList(childNodes.size()); + for (Node node : childNodes) { + if (node instanceof Element) + children.add((Element) node); + } + shadowChildrenRef = new SoftReference>(children); } - return new Elements(elements); + return children; + } + + /** + * Clears the cached shadow child elements. + */ + @Override + void nodelistChanged() { + super.nodelistChanged(); + shadowChildrenRef = null; } /** @@ -365,6 +387,25 @@ public Element insertChildren(int index, Collection children) { addChildren(index, nodeArray); return this; } + + /** + * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the + * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first. + * + * @param index 0-based index to insert children at. Specify {@code 0} to insert at the start, {@code -1} at the + * end + * @param children child nodes to insert + * @return this element, for chaining. + */ + public Element insertChildren(int index, Node... children) { + Validate.notNull(children, "Children collection to be inserted must not be null."); + int currentSize = childNodeSize(); + if (index < 0) index += currentSize +1; // roll around + Validate.isTrue(index >= 0 && index <= currentSize, "Insert position out of bounds."); + + addChildren(index, children); + return this; + } /** * Create a new element by tag name, and add it as the last child. @@ -553,7 +594,7 @@ public Elements siblingElements() { if (parentNode == null) return new Elements(0); - List elements = parent().children(); + List elements = parent().childElementsList(); Elements siblings = new Elements(elements.size() - 1); for (Element el: elements) if (el != this) @@ -572,7 +613,7 @@ public Elements siblingElements() { */ public Element nextElementSibling() { if (parentNode == null) return null; - List siblings = parent().children(); + List siblings = parent().childElementsList(); Integer index = indexInList(this, siblings); Validate.notNull(index); if (siblings.size() > index+1) @@ -588,7 +629,7 @@ public Element nextElementSibling() { */ public Element previousElementSibling() { if (parentNode == null) return null; - List siblings = parent().children(); + List siblings = parent().childElementsList(); Integer index = indexInList(this, siblings); Validate.notNull(index); if (index > 0) @@ -603,7 +644,7 @@ public Element previousElementSibling() { */ public Element firstElementSibling() { // todo: should firstSibling() exclude this? - List siblings = parent().children(); + List siblings = parent().childElementsList(); return siblings.size() > 1 ? siblings.get(0) : null; } @@ -614,7 +655,7 @@ public Element firstElementSibling() { */ public Integer elementSiblingIndex() { if (parent() == null) return 0; - return indexInList(this, parent().children()); + return indexInList(this, parent().childElementsList()); } /** @@ -622,7 +663,7 @@ public Integer elementSiblingIndex() { * @return the last sibling that is an element (aka the parent's last element child) */ public Element lastElementSibling() { - List siblings = parent().children(); + List siblings = parent().childElementsList(); return siblings.size() > 1 ? siblings.get(siblings.size() - 1) : null; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index ed19156538..55914236fe 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; +import org.jsoup.helper.ChangeNotifyingArrayList; import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.Parser; @@ -395,6 +396,10 @@ private Element getDeepChild(Element el) { else return el; } + + void nodelistChanged() { + // Element overrides this to clear its shadow children elements + } /** * Replace this node in the DOM with the supplied node. @@ -407,6 +412,7 @@ public void replaceWith(Node in) { } protected void setParentNode(Node parentNode) { + Validate.notNull(parentNode); if (this.parentNode != null) this.parentNode.removeChild(this); this.parentNode = parentNode; @@ -456,7 +462,7 @@ protected void addChildren(int index, Node... children) { protected void ensureChildNodes() { if (childNodes == EMPTY_NODES) { - childNodes = new ArrayList(4); + childNodes = new NodeList(4); } } @@ -665,7 +671,7 @@ protected Node doClone(Node parent) { clone.siblingIndex = parent == null ? 0 : siblingIndex; clone.attributes = attributes != null ? attributes.clone() : null; clone.baseUri = baseUri; - clone.childNodes = new ArrayList(childNodes.size()); + clone.childNodes = new NodeList(childNodes.size()); for (Node child: childNodes) clone.childNodes.add(child); @@ -700,4 +706,14 @@ public void tail(Node node, int depth) { } } } + + private final class NodeList extends ChangeNotifyingArrayList { + NodeList(int initialCapacity) { + super(initialCapacity); + } + + public void onContentsChanged() { + nodelistChanged(); + } + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index af7643d00d..6820d64273 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -750,7 +750,7 @@ public void insertChildrenArgumentValidation() { } try { - div2.insertChildren(0, null); + div2.insertChildren(0, (Collection) null); fail(); } catch (IllegalArgumentException e) { } @@ -1002,7 +1002,7 @@ public void testIs() { assertTrue(a.tagName().equals("P")); } - public void testChildrenElements() { + @Test public void testChildrenElements() { String html = "

One

Two

Three
Four"; Document doc = Jsoup.parse(html); Element div = doc.select("div").first(); @@ -1030,4 +1030,45 @@ public void testChildrenElements() { assertEquals(0, img.children().size()); assertEquals(0, img.childNodes().size()); } + + @Test public void testShadowElementsAreUpdated() { + String html = "

One

Two

Three
Four"; + Document doc = Jsoup.parse(html); + Element div = doc.select("div").first(); + Elements els = div.children(); + List nodes = div.childNodes(); + + assertEquals(2, els.size()); // the two Ps + assertEquals(3, nodes.size()); // the "Three" textnode + + Element p3 = new Element("p").text("P3"); + Element p4 = new Element("p").text("P4"); + div.insertChildren(1, p3); + div.insertChildren(3, p4); + Elements els2 = div.children(); + + // first els should not have changed + assertEquals(2, els.size()); + assertEquals(4, els2.size()); + + assertEquals("

One

\n" + + "

P3

\n" + + "

Two

\n" + + "

P4

Three", div.html()); + assertEquals("P3", els2.get(1).text()); + assertEquals("P4", els2.get(3).text()); + + p3.after("AnotherOne

\n" + + "

P3

\n" + + "Another\n" + + "

Two

\n" + + "

P4

Three", div.html()); + } } From fe4dfc24b7fae23e11fc38cb63039ca210c0eca6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 12:55:11 -0700 Subject: [PATCH 095/774] Changelog for 881 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 703771003d..ec2f011a77 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,10 @@ jsoup changelog * Bugfix: In Jsoup.Connection, if a redirect contained a query string with %xx escapes, they would be double escaped before the redirect was followed, leading to fetching an incorrect location. + * Bugfix: In Jsoup.Connection, if a request body was set and the connection was redirected, the body would incorrectly + still be sent. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. From 172dc2fd969b90135becca584919ca088fe367f2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 13:11:00 -0700 Subject: [PATCH 096/774] Couple tests for URL encoding issue --- src/test/java/org/jsoup/integration/UrlConnectTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 4dc7beffd5..5b81628f0b 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -813,4 +813,12 @@ public void run() { Document doc = res.parse(); assertEquals(200, res.statusCode()); } + + @Test public void handlesUnicodeInQuery() throws IOException { + Document doc = Jsoup.connect("https://www.google.pl/search?q=gąska").get(); + assertEquals("gąska - Szukaj w Google", doc.title()); + + doc = Jsoup.connect("http://mov-world.net/archiv/TV/A/%23No.Title/").get(); + assertEquals("Index of /archiv/TV/A/%23No.Title", doc.title()); + } } From bae55f9e99afed86b0d30caff999fd40c4268d48 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 13:18:42 -0700 Subject: [PATCH 097/774] Improved docs to help with #852 --- src/main/java/org/jsoup/nodes/Element.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 5b0315bce6..e3dac8f986 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1065,7 +1065,10 @@ public boolean hasText() { } /** - * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. + * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. Note that data is NOT the + * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@link #data()} + * for the contents of scripts, comments, CSS styles, etc. + * * @return the data, or empty string if none * * @see #dataNodes() From 07ce1cf7e37591038ed4e1a9ca966ba69350b5d0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 13:36:21 -0700 Subject: [PATCH 098/774] Change log --- src/main/java/org/jsoup/nodes/Element.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index e3dac8f986..355da52bbf 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1068,7 +1068,7 @@ public boolean hasText() { * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. Note that data is NOT the * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@link #data()} * for the contents of scripts, comments, CSS styles, etc. - * + * * @return the data, or empty string if none * * @see #dataNodes() From 824e82fff399c86cc3b7905ee6a952a848b2e69d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 13:57:16 -0700 Subject: [PATCH 099/774] Added method to clear all attributes --- CHANGES | 7 +++++++ src/main/java/org/jsoup/nodes/Node.java | 14 ++++++++++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 11 +++++++++++ 3 files changed, 32 insertions(+) diff --git a/CHANGES b/CHANGES index ec2f011a77..4be251c24c 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,9 @@ jsoup changelog * Improved the speed of index based CSS selectors and other methods that use elementSiblingIndex, by a factor of 18x. + * Added Node.clearAttributes(), to simplify removing of all attributes of a Node / Element. + + * Bugfix: if an attribute name started or ended with a control character, the parse would fail with a validation exception. @@ -25,6 +28,10 @@ jsoup changelog still be sent. + * Bugfix: In DataUtil when detecting the character set from meta data, and there are two Content-Types defined, use + the one that defines a character set. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 55914236fe..00eeb21f0b 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -130,6 +131,19 @@ public Node removeAttr(String attributeKey) { return this; } + /** + * Clear (remove) all of the attributes in this node. + * @return this, for chaining + */ + public Node clearAttributes() { + Iterator it = attributes.iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + return this; + } + /** Get the base URI of this node. @return base URI diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 6820d64273..3fcde8c9a5 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -973,6 +973,17 @@ public void testChainedRemoveAttributes() { assertEquals("Text", a.outerHtml()); } + @Test + public void testLoopedRemoveAttributes() { + String html = "Text

Two

"; + Document doc = Jsoup.parse(html); + for (Element el : doc.getAllElements()) { + el.clearAttributes(); + } + + assertEquals("Text\n

Two

", doc.body().html()); + } + @Test public void testIs() { String html = "

One Two Three

Another

"; From 2f6eabc9f0999421acf032c79f3106215c013580 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 14:30:10 -0700 Subject: [PATCH 100/774] Added test cases to validate case insentitivity of hasClass and selectors for class Fixes #830 --- .../java/org/jsoup/nodes/ElementTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 3fcde8c9a5..a2554edae2 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1082,4 +1082,39 @@ public void testIs() { "

Two

\n" + "

P4

Three", div.html()); } + + @Test public void classNamesAndAttributeNameIsCaseInsensitive() { + String html = "

One

"; + Document doc = Jsoup.parse(html); + Element p = doc.select("p").first(); + assertEquals("SomeText AnotherText", p.className()); + assertTrue(p.classNames().contains("SomeText")); + assertTrue(p.classNames().contains("AnotherText")); + assertTrue(p.hasClass("SomeText")); + assertTrue(p.hasClass("sometext")); + assertTrue(p.hasClass("AnotherText")); + assertTrue(p.hasClass("anothertext")); + + Element p1 = doc.select(".SomeText").first(); + Element p2 = doc.select(".sometext").first(); + Element p3 = doc.select("[class=SomeText AnotherText]").first(); + Element p4 = doc.select("[Class=SomeText AnotherText]").first(); + Element p5 = doc.select("[class=sometext anothertext]").first(); + Element p6 = doc.select("[class=SomeText AnotherText]").first(); + Element p7 = doc.select("[class^=sometext]").first(); + Element p8 = doc.select("[class$=nothertext]").first(); + Element p9 = doc.select("[class^=sometext]").first(); + Element p10 = doc.select("[class$=AnotherText]").first(); + + assertEquals("One", p1.text()); + assertEquals(p1, p2); + assertEquals(p1, p3); + assertEquals(p1, p4); + assertEquals(p1, p5); + assertEquals(p1, p6); + assertEquals(p1, p7); + assertEquals(p1, p8); + assertEquals(p1, p9); + assertEquals(p1, p10); + } } From b934c5d3e30917de86796c89fcb7cd000f642a80 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 15:51:45 -0700 Subject: [PATCH 101/774] Make sure unkown tags close correctly when in insensitive case mode Fixes #819 --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4be251c24c..e5624c9cf7 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,9 @@ jsoup changelog the one that defines a character set. + * Bugfix: when parsing unknown tags in case-sensitive HTML mode, end tags would not close scope correctly. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index c515462c77..e641a21de1 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -761,7 +761,7 @@ else if (!tb.onStack(formatEl)) { } boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { - String name = t.asEndTag().normalName(); + String name = t.asEndTag().name(); // matches with case sensitivity if enabled ArrayList stack = tb.getStack(); for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 4ab935918c..933810e1e3 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -946,4 +946,13 @@ public void testInvalidTableContents() throws IOException { Document doc = Jsoup.parse("

OneTwo

"); assertEquals("

OneTwo

", doc.body().html()); } + + @Test public void caseSensitiveParseTree() { + String html = "AB"; + Parser parser = Parser.htmlParser(); + parser.settings(ParseSettings.preserveCase); + Document doc = parser.parseInput(html, ""); + assertEquals(" A B ", StringUtil.normaliseWhitespace(doc.body().html())); + + } } From 1fd04fdb7be9ac08369394ef85537ad0ccaa2064 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 16:45:01 -0700 Subject: [PATCH 102/774] Remove content-type when being redirected, can't be on a Get Fixes #895 --- CHANGES | 3 +++ src/main/java/org/jsoup/helper/HttpConnection.java | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index e5624c9cf7..d9a86fe2a0 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,9 @@ jsoup changelog * Bugfix: when parsing unknown tags in case-sensitive HTML mode, end tags would not close scope correctly. + * In Jsoup.Connection, ensure there is no Content-Type set when being redirected to a GET. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index adfbc02fbe..42c5f1bded 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -663,6 +663,7 @@ else if (methodHasBody) req.method(Method.GET); // always redirect with a get. any data param from original req are dropped. req.data().clear(); req.requestBody(null); + req.removeHeader(CONTENT_TYPE); } String location = res.header(LOCATION); From 8799a6851c6582892b7fdf1d517adee8cc0a97b3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 10 Jun 2017 17:46:57 -0700 Subject: [PATCH 103/774] Changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index d9a86fe2a0..03acb2dfd4 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,9 @@ jsoup changelog * In Jsoup.Connection, ensure there is no Content-Type set when being redirected to a GET. + * Bugfix: in certain locales (Turkey specifically), lowercasing and case insensitivity could fail for specific items. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. From 42f17bac873279c4aba648d841258c6df8518302 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 11 Jun 2017 10:12:20 -0700 Subject: [PATCH 104/774] Use a weak ref instead of a soft ref for the shadow elements Uses a little less memory (no timestamp) and still provides cacheability when hit in a loop. --- src/main/java/org/jsoup/nodes/Element.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index c16b6cba95..cce2903365 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -14,7 +14,7 @@ import org.jsoup.select.Selector; import java.io.IOException; -import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,7 +38,7 @@ */ public class Element extends Node { private Tag tag; - private SoftReference> shadowChildrenRef; // points to child elements shadowed from node children + private WeakReference> shadowChildrenRef; // points to child elements shadowed from node children private static final Pattern classSplit = Pattern.compile("\\s+"); @@ -239,7 +239,7 @@ private List childElementsList() { if (node instanceof Element) children.add((Element) node); } - shadowChildrenRef = new SoftReference>(children); + shadowChildrenRef = new WeakReference>(children); } return children; } From 3c81151c7b4dd4739b11f0b6aa5eff81c07fd684 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 11 Jun 2017 10:46:22 -0700 Subject: [PATCH 105/774] Don't create Integer objects or Iterators Getting rid of the autoboxing effectively doubles the speed of the N-th selector benchmark (450 to 850 ops/s). And removing the Iterator lowers a bunch of GC. --- CHANGES | 2 +- src/main/java/org/jsoup/nodes/Element.java | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 03acb2dfd4..23e51e8edb 100644 --- a/CHANGES +++ b/CHANGES @@ -8,7 +8,7 @@ jsoup changelog * Improved selector validation for :contains(...) with unbalanced quotes. - * Improved the speed of index based CSS selectors and other methods that use elementSiblingIndex, by a factor of 18x. + * Improved the speed of index based CSS selectors and other methods that use elementSiblingIndex, by a factor of 34x. * Added Node.clearAttributes(), to simplify removing of all attributes of a Node / Element. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index cce2903365..4aad6db783 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -234,8 +234,10 @@ public Elements children() { private List childElementsList() { List children; if (shadowChildrenRef == null || (children = shadowChildrenRef.get()) == null) { - children = new ArrayList(childNodes.size()); - for (Node node : childNodes) { + final int size = childNodes.size(); + children = new ArrayList(size); + for (int i = 0; i < size; i++) { + final Node node = childNodes.get(i); if (node instanceof Element) children.add((Element) node); } @@ -655,7 +657,7 @@ public Element firstElementSibling() { * sibling, returns 0. * @return position in element sibling list */ - public Integer elementSiblingIndex() { + public int elementSiblingIndex() { if (parent() == null) return 0; return indexInList(this, parent().childElementsList()); } @@ -668,17 +670,13 @@ public Element lastElementSibling() { List siblings = parent().childElementsList(); return siblings.size() > 1 ? siblings.get(siblings.size() - 1) : null; } - - private static Integer indexInList(Element search, List elements) { - Validate.notNull(search); - Validate.notNull(elements); + private static int indexInList(Element search, List elements) { for (int i = 0; i < elements.size(); i++) { - E element = elements.get(i); - if (element == search) + if (elements.get(i) == search) return i; } - return null; + return 0; } // DOM type methods From 2a5f524d57545becc11c92a6a50b80283d22e594 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 11 Jun 2017 12:13:32 -0700 Subject: [PATCH 106/774] [maven-release-plugin] prepare release jsoup-1.10.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 184aaa729d..f680cff0ad 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.10.3-SNAPSHOT + 1.10.3 jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.10.3 Jonathan Hedley From 5e5c1dca1caba3473c34c0e2e5e9cf4d7b0d9ca1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 11 Jun 2017 12:13:41 -0700 Subject: [PATCH 107/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f680cff0ad..69ac6eecbd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.10.3 + 1.11.1-SNAPSHOT jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.10.3 + HEAD Jonathan Hedley From 75e1972b2b5d6afe8ee044ab760205ac687f5fe9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 11 Jun 2017 12:27:48 -0700 Subject: [PATCH 108/774] Release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 23e51e8edb..2f26941274 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.10.3 [PENDING] +*** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: List urls = doc.select("a").eachAttr("abs:href""); From bbe9c9747def8b62c5342a93ed6e47a872b191b9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 12 Jun 2017 18:54:51 -0700 Subject: [PATCH 109/774] Made Normalizer final for tiny speed hint Helps a little on Art runtime --- src/main/java/org/jsoup/internal/package-info.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/java/org/jsoup/internal/package-info.java diff --git a/src/main/java/org/jsoup/internal/package-info.java b/src/main/java/org/jsoup/internal/package-info.java new file mode 100644 index 0000000000..8fdca179b2 --- /dev/null +++ b/src/main/java/org/jsoup/internal/package-info.java @@ -0,0 +1,5 @@ +/** + * Util methods used by Jsoup. Please don't depend on the APIs implemented here as the contents may change without + * notice. + */ +package org.jsoup.internal; From 42c0a0ecf47607d3407f7b5db966160bf98cd30f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 12 Jun 2017 18:57:18 -0700 Subject: [PATCH 110/774] Normalizer is Final --- src/main/java/org/jsoup/internal/Normalizer.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/internal/Normalizer.java b/src/main/java/org/jsoup/internal/Normalizer.java index 02c31584d5..0583694a01 100644 --- a/src/main/java/org/jsoup/internal/Normalizer.java +++ b/src/main/java/org/jsoup/internal/Normalizer.java @@ -2,12 +2,16 @@ import java.util.Locale; -public class Normalizer { - public static String lowerCase(String input) { +/** + * Util methods for normalizing strings. Jsoup internal use only, please don't depend on this API. + */ +public final class Normalizer { + + public static String lowerCase(final String input) { return input.toLowerCase(Locale.ENGLISH); } - public static String normalize(String input) { + public static String normalize(final String input) { return lowerCase(input).trim(); } } From 1203bd459fe137e78a2a6dc2969f3c4995b726ce Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 12 Jun 2017 19:46:35 -0700 Subject: [PATCH 111/774] Make sure the HTML parser is correctly reset before reuse Otherwise the stack and current form element were stuffed if the DataUtil needed to reparse after switching content encoding. Also, was needlessly redecoding when the character set was "utf-8" because the check was case sensitive. --- CHANGES | 5 +++ src/main/java/org/jsoup/helper/DataUtil.java | 3 +- .../org/jsoup/parser/HtmlTreeBuilder.java | 38 ++++++++++++++----- .../java/org/jsoup/parser/TreeBuilder.java | 1 + .../java/org/jsoup/integration/ParseTest.java | 10 +++++ .../htmltests/lowercase-charset-test.html | 14 +++++++ 6 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/htmltests/lowercase-charset-test.html diff --git a/CHANGES b/CHANGES index 2f26941274..f467f3937f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,10 @@ jsoup changelog +*** Release 1.11.1 [PENDING] + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, + which could lead to an incorrect DOM. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 44a4db39bc..888a586334 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -124,8 +124,7 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba } } foundCharset = validateCharset(foundCharset); - - if (foundCharset != null && !foundCharset.equals(defaultCharset)) { // need to re-decode + if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharset)) { // need to re-decode. (case insensitive check here to match how validate works) foundCharset = foundCharset.trim().replaceAll("[\"']", ""); charsetName = foundCharset; byteData.rewind(); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 2078a9b984..663d7ab90d 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -2,7 +2,13 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; -import org.jsoup.nodes.*; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.FormElement; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import java.util.ArrayList; @@ -31,17 +37,17 @@ public class HtmlTreeBuilder extends TreeBuilder { private HtmlTreeBuilderState state; // the current state private HtmlTreeBuilderState originalState; // original / marked state - private boolean baseUriSetFromDoc = false; + private boolean baseUriSetFromDoc; private Element headElement; // the current head element private FormElement formElement; // the current form element private Element contextElement; // fragment parse context -- could be null even if fragment parsing - private ArrayList formattingElements = new ArrayList(); // active (open) formatting elements - private List pendingTableCharacters = new ArrayList(); // chars in table to be shifted out - private Token.EndTag emptyEnd = new Token.EndTag(); // reused empty end tag + private ArrayList formattingElements; // active (open) formatting elements + private List pendingTableCharacters; // chars in table to be shifted out + private Token.EndTag emptyEnd; // reused empty end tag - private boolean framesetOk = true; // if ok to go into frameset - private boolean fosterInserts = false; // if next inserts should be fostered - private boolean fragmentParsing = false; // if parsing a fragment of html + private boolean framesetOk; // if ok to go into frameset + private boolean fosterInserts; // if next inserts should be fostered + private boolean fragmentParsing; // if parsing a fragment of html HtmlTreeBuilder() {} @@ -50,10 +56,22 @@ ParseSettings defaultSettings() { } @Override - Document parse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { + protected void initialiseParse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { + super.initialiseParse(input, baseUri, errors, settings); + + // this is a bit mucky. todo - probably just create new parser objects to ensure all reset. state = HtmlTreeBuilderState.Initial; + originalState = null; baseUriSetFromDoc = false; - return super.parse(input, baseUri, errors, settings); + headElement = null; + formElement = null; + contextElement = null; + formattingElements = new ArrayList(); + pendingTableCharacters = new ArrayList(); + emptyEnd = new Token.EndTag(); + framesetOk = true; + fosterInserts = false; + fragmentParsing = false; } List parseFragment(String inputFragment, Element context, String baseUri, ParseErrorList errors, ParseSettings settings) { diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 445c57e768..14ff888b65 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -33,6 +33,7 @@ protected void initialiseParse(String input, String baseUri, ParseErrorList erro this.settings = settings; reader = new CharacterReader(input); this.errors = errors; + currentToken = null; tokeniser = new Tokeniser(reader, errors); stack = new ArrayList(32); this.baseUri = baseUri; diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index ae14bed2fe..dcf35ace58 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -168,6 +168,16 @@ public void testYahooArticle() throws IOException { assertEquals("In July, GM said its electric Chevrolet Volt will be sold in the United States at $41,000 -- $8,000 more than its nearest competitor, the Nissan Leaf.", p.text()); } + @Test + public void testLowercaseUtf8Charset() throws IOException { + File in = getFile("/htmltests/lowercase-charset-test.html"); + Document doc = Jsoup.parse(in, null); + + Element form = doc.select("#form").first(); + assertEquals(2, form.children().size()); + assertEquals("UTF-8", doc.outputSettings().charset().name()); + } + public static File getFile(String resourceName) { try { File file = new File(ParseTest.class.getResource(resourceName).toURI()); diff --git a/src/test/resources/htmltests/lowercase-charset-test.html b/src/test/resources/htmltests/lowercase-charset-test.html new file mode 100644 index 0000000000..c1625a3015 --- /dev/null +++ b/src/test/resources/htmltests/lowercase-charset-test.html @@ -0,0 +1,14 @@ + + + +FormData: has + + + + +
+
+ + +
+
From e2ff399dfdb1d4cf4ebb639d1fc2689aa2f6f3eb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 17 Jun 2017 15:36:28 -0700 Subject: [PATCH 112/774] Updated from Java 5 to Java 7 --- .travis.yml | 2 +- CHANGES | 4 + pom.xml | 6 +- .../jsoup/helper/DescendableLinkedList.java | 83 ---------------- .../java/org/jsoup/helper/HttpConnection.java | 14 ++- src/main/java/org/jsoup/helper/W3CDom.java | 2 +- src/main/java/org/jsoup/nodes/Attributes.java | 10 +- src/main/java/org/jsoup/nodes/Document.java | 4 +- src/main/java/org/jsoup/nodes/Element.java | 13 +-- src/main/java/org/jsoup/nodes/Entities.java | 2 +- .../java/org/jsoup/nodes/FormElement.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 7 +- .../org/jsoup/parser/HtmlTreeBuilder.java | 8 +- .../jsoup/parser/HtmlTreeBuilderState.java | 95 ++++++++++--------- src/main/java/org/jsoup/parser/Tag.java | 2 +- .../java/org/jsoup/parser/TreeBuilder.java | 2 +- src/main/java/org/jsoup/safety/Whitelist.java | 18 ++-- .../org/jsoup/select/CombiningEvaluator.java | 2 +- src/main/java/org/jsoup/select/Elements.java | 8 +- .../java/org/jsoup/select/QueryParser.java | 2 +- src/main/java/org/jsoup/select/Selector.java | 4 +- 21 files changed, 110 insertions(+), 180 deletions(-) delete mode 100644 src/main/java/org/jsoup/helper/DescendableLinkedList.java diff --git a/.travis.yml b/.travis.yml index 38d05239d2..ee9d09c192 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: java jdk: - - openjdk6 - openjdk7 + - openjdk8 - oraclejdk7 - oraclejdk8 diff --git a/CHANGES b/CHANGES index f467f3937f..be92bf13c8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog *** Release 1.11.1 [PENDING] + * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are + not used. + + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. diff --git a/pom.xml b/pom.xml index 69ac6eecbd..fd8fba36d4 100644 --- a/pom.xml +++ b/pom.xml @@ -38,8 +38,8 @@ maven-compiler-plugin 3.5.1 - 1.5 - 1.5 + 1.7 + 1.7 UTF-8 @@ -58,7 +58,7 @@ org.codehaus.mojo.signature - java15 + java17 1.0 diff --git a/src/main/java/org/jsoup/helper/DescendableLinkedList.java b/src/main/java/org/jsoup/helper/DescendableLinkedList.java deleted file mode 100644 index 833a6a220b..0000000000 --- a/src/main/java/org/jsoup/helper/DescendableLinkedList.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.jsoup.helper; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.ListIterator; - -/** - * Provides a descending iterator and other 1.6 methods to allow support on the 1.5 JRE. - * @param Type of elements - */ -public class DescendableLinkedList extends LinkedList { - - /** - * Create a new DescendableLinkedList. - */ - public DescendableLinkedList() { - super(); - } - - /** - * Add a new element to the start of the list. - * @param e element to add - */ - public void push(E e) { - addFirst(e); - } - - /** - * Look at the last element, if there is one. - * @return the last element, or null - */ - public E peekLast() { - return size() == 0 ? null : getLast(); - } - - /** - * Remove and return the last element, if there is one - * @return the last element, or null - */ - public E pollLast() { - return size() == 0 ? null : removeLast(); - } - - /** - * Get an iterator that starts and the end of the list and works towards the start. - * @return an iterator that starts and the end of the list and works towards the start. - */ - public Iterator descendingIterator() { - return new DescendingIterator(size()); - } - - private class DescendingIterator implements Iterator { - private final ListIterator iter; - - @SuppressWarnings("unchecked") - private DescendingIterator(int index) { - iter = (ListIterator) listIterator(index); - } - - /** - * Check if there is another element on the list. - * @return if another element - */ - public boolean hasNext() { - return iter.hasPrevious(); - } - - /** - * Get the next element. - * @return the next element. - */ - public E next() { - return iter.previous(); - } - - /** - * Remove the current element. - */ - public void remove() { - iter.remove(); - } - } -} diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 9f1b70d6e7..5cb1f9fed3 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -293,8 +293,8 @@ private static abstract class Base implements Connect Map cookies; private Base() { - headers = new LinkedHashMap(); - cookies = new LinkedHashMap(); + headers = new LinkedHashMap<>(); + cookies = new LinkedHashMap<>(); } public URL url() { @@ -477,7 +477,7 @@ private Request() { timeoutMilliseconds = 30000; // 30 seconds maxBodySizeBytes = 1024 * 1024; // 1MB followRedirects = true; - data = new ArrayList(); + data = new ArrayList<>(); method = Method.GET; headers.put("Accept-Encoding", "gzip"); headers.put(USER_AGENT, DEFAULT_UA); @@ -848,9 +848,7 @@ public X509Certificate[] getAcceptedIssuers() { sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); // Create an ssl socket factory with our all-trusting manager sslSocketFactory = sslContext.getSocketFactory(); - } catch (NoSuchAlgorithmException e) { - throw new IOException("Can't create unsecure trust manager"); - } catch (KeyManagementException e) { + } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new IOException("Can't create unsecure trust manager"); } } @@ -879,7 +877,7 @@ private void setupFromConnection(HttpURLConnection conn, Connection.Response pre private static LinkedHashMap> createHeaderMap(HttpURLConnection conn) { // the default sun impl of conn.getHeaderFields() returns header values out of order - final LinkedHashMap> headers = new LinkedHashMap>(); + final LinkedHashMap> headers = new LinkedHashMap<>(); int i = 0; while (true) { final String key = conn.getHeaderFieldKey(i); @@ -893,7 +891,7 @@ private static LinkedHashMap> createHeaderMap(HttpURLConnec if (headers.containsKey(key)) headers.get(key).add(val); else { - final ArrayList vals = new ArrayList(); + final ArrayList vals = new ArrayList<>(); vals.add(val); headers.put(key, vals); } diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index d62eb9dfc7..27d45c9566 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -71,7 +71,7 @@ protected static class W3CBuilder implements NodeVisitor { private static final String xmlnsPrefix = "xmlns:"; private final Document doc; - private final HashMap namespaces = new HashMap(); // prefix => urn + private final HashMap namespaces = new HashMap<>(); // prefix => urn private Element dest; public W3CBuilder(Document doc) { diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 61844eeaf1..ffe77ab235 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -100,7 +100,7 @@ public void put(String key, boolean value) { public void put(Attribute attribute) { Validate.notNull(attribute); if (attributes == null) - attributes = new LinkedHashMap(2); + attributes = new LinkedHashMap<>(2); attributes.put(attribute.getKey(), attribute); } @@ -172,7 +172,7 @@ public void addAll(Attributes incoming) { if (incoming.size() == 0) return; if (attributes == null) - attributes = new LinkedHashMap(incoming.size()); + attributes = new LinkedHashMap<>(incoming.size()); attributes.putAll(incoming.attributes); } @@ -193,7 +193,7 @@ public List asList() { if (attributes == null) return Collections.emptyList(); - List list = new ArrayList(attributes.size()); + List list = new ArrayList<>(attributes.size()); for (Map.Entry entry : attributes.entrySet()) { list.add(entry.getValue()); } @@ -275,7 +275,7 @@ public Attributes clone() { } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } - clone.attributes = new LinkedHashMap(attributes.size()); + clone.attributes = new LinkedHashMap<>(attributes.size()); for (Attribute attribute: this) clone.attributes.put(attribute.getKey(), attribute.clone()); return clone; @@ -285,7 +285,7 @@ private class Dataset extends AbstractMap { private Dataset() { if (attributes == null) - attributes = new LinkedHashMap(2); + attributes = new LinkedHashMap<>(2); } @Override diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 62197da5f4..b5b74aed74 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -137,7 +137,7 @@ public Document normalise() { // does not recurse. private void normaliseTextNodes(Element element) { - List toMove = new ArrayList(); + List toMove = new ArrayList<>(); for (Node node: element.childNodes) { if (node instanceof TextNode) { TextNode tn = (TextNode) node; @@ -159,7 +159,7 @@ private void normaliseStructure(String tag, Element htmlEl) { Elements elements = this.getElementsByTag(tag); Element master = elements.first(); // will always be available as created above if not existent if (elements.size() > 1) { // dupes, move contents to master - List toMove = new ArrayList(); + List toMove = new ArrayList<>(); for (int i = 1; i < elements.size(); i++) { Node dupe = elements.get(i); for (Node node : dupe.childNodes) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 4aad6db783..72a5b5f6ca 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -235,13 +235,14 @@ private List childElementsList() { List children; if (shadowChildrenRef == null || (children = shadowChildrenRef.get()) == null) { final int size = childNodes.size(); - children = new ArrayList(size); + children = new ArrayList<>(size); + //noinspection ForLoopReplaceableByForEach (beacause it allocates an Iterator which is wasteful here) for (int i = 0; i < size; i++) { final Node node = childNodes.get(i); if (node instanceof Element) children.add((Element) node); } - shadowChildrenRef = new WeakReference>(children); + shadowChildrenRef = new WeakReference<>(children); } return children; } @@ -272,7 +273,7 @@ void nodelistChanged() { * */ public List textNodes() { - List textNodes = new ArrayList(); + List textNodes = new ArrayList<>(); for (Node node : childNodes) { if (node instanceof TextNode) textNodes.add((TextNode) node); @@ -290,7 +291,7 @@ public List textNodes() { * @see #data() */ public List dataNodes() { - List dataNodes = new ArrayList(); + List dataNodes = new ArrayList<>(); for (Node node : childNodes) { if (node instanceof DataNode) dataNodes.add((DataNode) node); @@ -386,7 +387,7 @@ public Element insertChildren(int index, Collection children) { if (index < 0) index += currentSize +1; // roll around Validate.isTrue(index >= 0 && index <= currentSize, "Insert position out of bounds."); - ArrayList nodes = new ArrayList(children); + ArrayList nodes = new ArrayList<>(children); Node[] nodeArray = nodes.toArray(new Node[nodes.size()]); addChildren(index, nodeArray); return this; @@ -1109,7 +1110,7 @@ public String className() { */ public Set classNames() { String[] names = classSplit.split(className()); - Set classNames = new LinkedHashSet(Arrays.asList(names)); + Set classNames = new LinkedHashSet<>(Arrays.asList(names)); classNames.remove(""); // if classNames() was empty, would include an empty class return classNames; diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index d7e5d4999b..1c10d1f52b 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -74,7 +74,7 @@ private int size() { } } - private static final HashMap multipoints = new HashMap(); // name -> multiple character references + private static final HashMap multipoints = new HashMap<>(); // name -> multiple character references private Entities() { } diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index 72dac82c5f..6ef6b34a3b 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -70,7 +70,7 @@ public Connection submit() { * @return a list of key vals */ public List formData() { - ArrayList data = new ArrayList(); + ArrayList data = new ArrayList<>(); // iterate the form control elements and accumulate their values for (Element el: elements) { diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 44f2dbdc36..3380453fa4 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -228,7 +228,7 @@ public List childNodes() { * @return a deep copy of this node's children */ public List childNodesCopy() { - List children = new ArrayList(childNodes.size()); + List children = new ArrayList<>(childNodes.size()); for (Node node : childNodes) { children.add(node.clone()); } @@ -371,6 +371,7 @@ public Node wrap(String html) { // remainder (unbalanced wrap, like

-- The

is remainder if (wrapChildren.size() > 0) { + //noinspection ForLoopReplaceableByForEach (beacause it allocates an Iterator which is wasteful here) for (int i = 0; i < wrapChildren.size(); i++) { Node remainder = wrapChildren.get(i); remainder.parentNode.removeChild(remainder); @@ -504,7 +505,7 @@ public List siblingNodes() { return Collections.emptyList(); List nodes = parentNode.childNodes; - List siblings = new ArrayList(nodes.size() - 1); + List siblings = new ArrayList<>(nodes.size() - 1); for (Node node: nodes) if (node != this) siblings.add(node); @@ -654,7 +655,7 @@ public Node clone() { Node thisClone = doClone(null); // splits for orphan // Queue up nodes that need their children cloned (BFS). - LinkedList nodesToProcess = new LinkedList(); + LinkedList nodesToProcess = new LinkedList<>(); nodesToProcess.add(thisClone); while (!nodesToProcess.isEmpty()) { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 663d7ab90d..8667a8d96e 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -19,7 +19,7 @@ */ public class HtmlTreeBuilder extends TreeBuilder { // tag searches - public static final String[] TagsSearchInScope = new String[]{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}; + private static final String[] TagsSearchInScope = new String[]{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}; private static final String[] TagSearchList = new String[]{"ol", "ul"}; private static final String[] TagSearchButton = new String[]{"button"}; private static final String[] TagSearchTableScope = new String[]{"html", "table"}; @@ -66,8 +66,8 @@ protected void initialiseParse(String input, String baseUri, ParseErrorList erro headElement = null; formElement = null; contextElement = null; - formattingElements = new ArrayList(); - pendingTableCharacters = new ArrayList(); + formattingElements = new ArrayList<>(); + pendingTableCharacters = new ArrayList<>(); emptyEnd = new Token.EndTag(); framesetOk = true; fosterInserts = false; @@ -536,7 +536,7 @@ void setFormElement(FormElement formElement) { } void newPendingTableCharacters() { - pendingTableCharacters = new ArrayList(); + pendingTableCharacters = new ArrayList<>(); } List getPendingTableCharacters() { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index a5dec1d59d..9a118ebffc 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -271,6 +271,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } case StartTag: Token.StartTag startTag = t.asStartTag(); + // todo - refactor to a switch statement String name = startTag.normalName(); if (name.equals("a")) { if (tb.getActiveFormattingElement("a") != null) { @@ -987,18 +988,19 @@ boolean process(Token t, HtmlTreeBuilder tb) { break; case StartTag: Token.StartTag startTag = t.asStartTag(); - String name = startTag.normalName(); - if (name.equals("html")) - return tb.process(t, InBody); - else if (name.equals("col")) - tb.insertEmpty(startTag); - else - return anythingElse(t, tb); + switch (startTag.normalName()) { + case "html": + return tb.process(t, InBody); + case "col": + tb.insertEmpty(startTag); + break; + default: + return anythingElse(t, tb); + } break; case EndTag: Token.EndTag endTag = t.asEndTag(); - name = endTag.normalName(); - if (name.equals("colgroup")) { + if (endTag.normalName.equals("colgroup")) { if (tb.currentElement().nodeName().equals("html")) { // frag case tb.error(this); return false; @@ -1256,28 +1258,33 @@ else if (tb.currentElement().nodeName().equals("optgroup")) case EndTag: Token.EndTag end = t.asEndTag(); name = end.normalName(); - if (name.equals("optgroup")) { - if (tb.currentElement().nodeName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).nodeName().equals("optgroup")) - tb.processEndTag("option"); - if (tb.currentElement().nodeName().equals("optgroup")) - tb.pop(); - else - tb.error(this); - } else if (name.equals("option")) { - if (tb.currentElement().nodeName().equals("option")) - tb.pop(); - else - tb.error(this); - } else if (name.equals("select")) { - if (!tb.inSelectScope(name)) { - tb.error(this); - return false; - } else { - tb.popStackToClose(name); - tb.resetInsertionMode(); - } - } else - return anythingElse(t, tb); + switch (name) { + case "optgroup": + if (tb.currentElement().nodeName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).nodeName().equals("optgroup")) + tb.processEndTag("option"); + if (tb.currentElement().nodeName().equals("optgroup")) + tb.pop(); + else + tb.error(this); + break; + case "option": + if (tb.currentElement().nodeName().equals("option")) + tb.pop(); + else + tb.error(this); + break; + case "select": + if (!tb.inSelectScope(name)) { + tb.error(this); + return false; + } else { + tb.popStackToClose(name); + tb.resetInsertionMode(); + } + break; + default: + return anythingElse(t, tb); + } break; case EOF: if (!tb.currentElement().nodeName().equals("html")) @@ -1351,18 +1358,20 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else if (t.isStartTag()) { Token.StartTag start = t.asStartTag(); - String name = start.normalName(); - if (name.equals("html")) { - return tb.process(start, InBody); - } else if (name.equals("frameset")) { - tb.insert(start); - } else if (name.equals("frame")) { - tb.insertEmpty(start); - } else if (name.equals("noframes")) { - return tb.process(start, InHead); - } else { - tb.error(this); - return false; + switch (start.normalName()) { + case "html": + return tb.process(start, InBody); + case "frameset": + tb.insert(start); + break; + case "frame": + tb.insertEmpty(start); + break; + case "noframes": + return tb.process(start, InHead); + default: + tb.error(this); + return false; } } else if (t.isEndTag() && t.asEndTag().normalName().equals("frameset")) { if (tb.currentElement().nodeName().equals("html")) { // frag diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index bebeaaee0b..0dcbbdb2a6 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -11,7 +11,7 @@ * @author Jonathan Hedley, jonathan@hedley.net */ public class Tag { - private static final Map tags = new HashMap(); // map of known tags + private static final Map tags = new HashMap<>(); // map of known tags private String tagName; private boolean isBlock = true; // block or inline diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 14ff888b65..6ff1bad84b 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -35,7 +35,7 @@ protected void initialiseParse(String input, String baseUri, ParseErrorList erro this.errors = errors; currentToken = null; tokeniser = new Tokeniser(reader, errors); - stack = new ArrayList(32); + stack = new ArrayList<>(32); this.baseUri = baseUri; } diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index 8258ce9528..d363bc2479 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -190,10 +190,10 @@ public static Whitelist relaxed() { @see #relaxed() */ public Whitelist() { - tagNames = new HashSet(); - attributes = new HashMap>(); - enforcedAttributes = new HashMap>(); - protocols = new HashMap>>(); + tagNames = new HashSet<>(); + attributes = new HashMap<>(); + enforcedAttributes = new HashMap<>(); + protocols = new HashMap<>(); preserveRelativeLinks = false; } @@ -258,7 +258,7 @@ public Whitelist addAttributes(String tag, String... attributes) { TagName tagName = TagName.valueOf(tag); if (!tagNames.contains(tagName)) tagNames.add(tagName); - Set attributeSet = new HashSet(); + Set attributeSet = new HashSet<>(); for (String key : attributes) { Validate.notEmpty(key); attributeSet.add(AttributeKey.valueOf(key)); @@ -293,7 +293,7 @@ public Whitelist removeAttributes(String tag, String... attributes) { Validate.isTrue(attributes.length > 0, "No attribute names supplied."); TagName tagName = TagName.valueOf(tag); - Set attributeSet = new HashSet(); + Set attributeSet = new HashSet<>(); for (String key : attributes) { Validate.notEmpty(key); attributeSet.add(AttributeKey.valueOf(key)); @@ -343,7 +343,7 @@ public Whitelist addEnforcedAttribute(String tag, String attribute, String value if (enforcedAttributes.containsKey(tagName)) { enforcedAttributes.get(tagName).put(attrKey, attrVal); } else { - Map attrMap = new HashMap(); + Map attrMap = new HashMap<>(); attrMap.put(attrKey, attrVal); enforcedAttributes.put(tagName, attrMap); } @@ -422,13 +422,13 @@ public Whitelist addProtocols(String tag, String attribute, String... protocols) if (this.protocols.containsKey(tagName)) { attrMap = this.protocols.get(tagName); } else { - attrMap = new HashMap>(); + attrMap = new HashMap<>(); this.protocols.put(tagName, attrMap); } if (attrMap.containsKey(attrKey)) { protSet = attrMap.get(attrKey); } else { - protSet = new HashSet(); + protSet = new HashSet<>(); attrMap.put(attrKey, protSet); } for (String protocol : protocols) { diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index e3616f4689..9cb163a4a1 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -16,7 +16,7 @@ abstract class CombiningEvaluator extends Evaluator { CombiningEvaluator() { super(); - evaluators = new ArrayList(); + evaluators = new ArrayList<>(); } CombiningEvaluator(Collection evaluators) { diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 246c9436ae..68f9773ced 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -85,7 +85,7 @@ public boolean hasAttr(String attributeKey) { * @return a list of each element's attribute value for the attribute */ public List eachAttr(String attributeKey) { - List attrs = new ArrayList(size()); + List attrs = new ArrayList<>(size()); for (Element element : this) { if (element.hasAttr(attributeKey)) attrs.add(element.attr(attributeKey)); @@ -231,7 +231,7 @@ public boolean hasText() { * @see #text() */ public List eachText() { - ArrayList texts = new ArrayList(size()); + ArrayList texts = new ArrayList<>(size()); for (Element el: this) { if (el.hasText()) texts.add(el.text()); @@ -577,7 +577,7 @@ else if (sib.is(eval)) * @return all of the parents and ancestor elements of the matched elements */ public Elements parents() { - HashSet combo = new LinkedHashSet(); + HashSet combo = new LinkedHashSet<>(); for (Element e: this) { combo.addAll(e.parents()); } @@ -621,7 +621,7 @@ public Elements traverse(NodeVisitor nodeVisitor) { * no forms. */ public List forms() { - ArrayList forms = new ArrayList(); + ArrayList forms = new ArrayList<>(); for (Element el: this) if (el instanceof FormElement) forms.add((FormElement) el); diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 46c6c2dd41..bb5a98ece0 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -20,7 +20,7 @@ public class QueryParser { private TokenQueue tq; private String query; - private List evals = new ArrayList(); + private List evals = new ArrayList<>(); /** * Create a new QueryParser. diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 715508fd07..d0130c09f4 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -130,8 +130,8 @@ public static Elements select(String query, Iterable roots) { Validate.notEmpty(query); Validate.notNull(roots); Evaluator evaluator = QueryParser.parse(query); - ArrayList elements = new ArrayList(); - IdentityHashMap seenElements = new IdentityHashMap(); + ArrayList elements = new ArrayList<>(); + IdentityHashMap seenElements = new IdentityHashMap<>(); // dedupe elements by identity, not equality for (Element root : roots) { From 5840fcd4a19497eff124691aef9b3bcf43ec2f55 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 17 Jun 2017 15:41:38 -0700 Subject: [PATCH 113/774] Incantation to make Travis CI work with OpenJDK Per https://github.com/travis-ci/travis-ci/issues/6483 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ee9d09c192..631d0377d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: java +dist: trusty + jdk: - openjdk7 - openjdk8 From ad4a1bee132ee7d1167ac795407345f2bc6a1db8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 17 Jun 2017 15:49:10 -0700 Subject: [PATCH 114/774] Minor inspections --- src/main/java/org/jsoup/nodes/Document.java | 3 +-- src/main/java/org/jsoup/nodes/Node.java | 3 +-- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/main/java/org/jsoup/parser/Tokeniser.java | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index b5b74aed74..638e5067a0 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -162,8 +162,7 @@ private void normaliseStructure(String tag, Element htmlEl) { List toMove = new ArrayList<>(); for (int i = 1; i < elements.size(); i++) { Node dupe = elements.get(i); - for (Node node : dupe.childNodes) - toMove.add(node); + toMove.addAll(dupe.childNodes); dupe.remove(); } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 3380453fa4..2fa56c02b0 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -690,8 +690,7 @@ protected Node doClone(Node parent) { clone.baseUri = baseUri; clone.childNodes = new NodeList(childNodes.size()); - for (Node child: childNodes) - clone.childNodes.add(child); + clone.childNodes.addAll(childNodes); return clone; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 8667a8d96e..4f0d0fc567 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -119,7 +119,7 @@ else if (contextTag.equals("plaintext")) } runParser(); - if (context != null && root != null) + if (context != null) return root.childNodes(); else return doc.childNodes(); diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index b793c38780..1526c2ad69 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -152,7 +152,7 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in try { int base = isHexMode ? 16 : 10; charval = Integer.valueOf(numRef, base); - } catch (NumberFormatException e) { + } catch (NumberFormatException ignored) { } // skip if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) { characterReferenceError("character outside of valid range"); From 654bc6f981587bd4b39b66702550bad33e1eacc9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 24 Jun 2017 23:47:45 -0700 Subject: [PATCH 115/774] Stream network loading (#905) Implements a stream based input reader. This changes URL & file inputs from buffering the whole content into a string before starting to parse, into a stream based read. Memory consumption when loading large pages from the network or a file is substantially reduced. --- .travis.yml | 3 - CHANGES | 5 + .../java/org/jsoup/UncheckedIOException.java | 13 + src/main/java/org/jsoup/helper/DataUtil.java | 103 ++++--- .../java/org/jsoup/helper/HttpConnection.java | 90 ++++-- src/main/java/org/jsoup/helper/W3CDom.java | 2 +- .../internal/ConstrainableInputStream.java | 35 +++ src/main/java/org/jsoup/nodes/Entities.java | 76 +++-- .../org/jsoup/parser/CharacterReader.java | 284 +++++++++++------- .../org/jsoup/parser/HtmlTreeBuilder.java | 6 +- src/main/java/org/jsoup/parser/Parser.java | 11 +- .../java/org/jsoup/parser/TokeniserState.java | 2 +- .../java/org/jsoup/parser/TreeBuilder.java | 5 +- .../java/org/jsoup/parser/XmlTreeBuilder.java | 12 +- .../java/org/jsoup/select/QueryParser.java | 4 +- .../java/org/jsoup/helper/DataUtilTest.java | 83 ++--- .../org/jsoup/helper/HttpConnectionTest.java | 23 +- .../org/jsoup/integration/UrlConnectTest.java | 83 ++++- .../org/jsoup/parser/CharacterReaderTest.java | 12 + 19 files changed, 561 insertions(+), 291 deletions(-) create mode 100644 src/main/java/org/jsoup/UncheckedIOException.java create mode 100644 src/main/java/org/jsoup/internal/ConstrainableInputStream.java diff --git a/.travis.yml b/.travis.yml index 631d0377d8..4337408f63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,7 @@ language: java -dist: trusty - jdk: - openjdk7 - - openjdk8 - oraclejdk7 - oraclejdk8 diff --git a/CHANGES b/CHANGES index be92bf13c8..9109fc1e44 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,11 @@ jsoup changelog not used. + * When loading content from a URL or a file, the content is now parsed as it streams in from the network or disk, + rather than being fully buffered before parsing. This substantially reduces memory consumption & large garbage + objects when loading large files. + + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. diff --git a/src/main/java/org/jsoup/UncheckedIOException.java b/src/main/java/org/jsoup/UncheckedIOException.java new file mode 100644 index 0000000000..e9a91df903 --- /dev/null +++ b/src/main/java/org/jsoup/UncheckedIOException.java @@ -0,0 +1,13 @@ +package org.jsoup; + +import java.io.IOException; + +public class UncheckedIOException extends Error { + public UncheckedIOException(IOException cause) { + super(cause); + } + + public IOException ioException() { + return (IOException) getCause(); + } +} diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 888a586334..b6724ca7a5 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -1,15 +1,19 @@ package org.jsoup.helper; +import org.jsoup.internal.ConstrainableInputStream; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.XmlDeclaration; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; @@ -25,9 +29,10 @@ * */ public final class DataUtil { - private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:\"|')?([^\\s,;\"']*)"); + private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:[\"'])?([^\\s,;\"']*)"); static final String defaultCharset = "UTF-8"; // used if not found in header or meta charset - private static final int bufferSize = 60000; + private static final int firstReadBufferSize = 1024 * 5; + static final int bufferSize = 1024 * 32; private static final char[] mimeBoundaryChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); static final int boundaryLength = 32; @@ -43,8 +48,7 @@ private DataUtil() {} * @throws IOException on IO error */ public static Document load(File in, String charsetName, String baseUri) throws IOException { - ByteBuffer byteData = readFileToByteBuffer(in); - return parseByteData(byteData, charsetName, baseUri, Parser.htmlParser()); + return parseInputStream(new FileInputStream(in), charsetName, baseUri, Parser.htmlParser()); } /** @@ -56,8 +60,7 @@ public static Document load(File in, String charsetName, String baseUri) throws * @throws IOException on IO error */ public static Document load(InputStream in, String charsetName, String baseUri) throws IOException { - ByteBuffer byteData = readToByteBuffer(in); - return parseByteData(byteData, charsetName, baseUri, Parser.htmlParser()); + return parseInputStream(in, charsetName, baseUri, Parser.htmlParser()); } /** @@ -70,8 +73,7 @@ public static Document load(InputStream in, String charsetName, String baseUri) * @throws IOException on IO error */ public static Document load(InputStream in, String charsetName, String baseUri, Parser parser) throws IOException { - ByteBuffer byteData = readToByteBuffer(in); - return parseByteData(byteData, charsetName, baseUri, parser); + return parseInputStream(in, charsetName, baseUri, parser); } /** @@ -88,57 +90,70 @@ static void crossStreams(final InputStream in, final OutputStream out) throws IO } } - // reads bytes first into a buffer, then decodes with the appropriate charset. done this way to support - // switching the chartset midstream when a meta http-equiv tag defines the charset. - // todo - this is getting gnarly. needs a rewrite. - static Document parseByteData(ByteBuffer byteData, String charsetName, String baseUri, Parser parser) { - String docData; + static Document parseInputStream(InputStream input, String charsetName, String baseUri, Parser parser) throws IOException { + if (input == null) // empty body + return new Document(baseUri); + + if (!(input instanceof ConstrainableInputStream)) + input = new ConstrainableInputStream(input, bufferSize, 0); + Document doc = null; + boolean fullyRead = false; + + // read the start of the stream and look for a BOM or meta charset + input.mark(firstReadBufferSize); + ByteBuffer firstBytes = readToByteBuffer(input, firstReadBufferSize - 1); // -1 because we read one more to see if completed + fullyRead = input.read() == -1; + input.reset(); // look for BOM - overrides any other header or input - charsetName = detectCharsetFromBom(byteData, charsetName); + BomCharset bomCharset = detectCharsetFromBom(firstBytes, charsetName); + if (bomCharset != null) { + charsetName = bomCharset.charset; + input.skip(bomCharset.offset); + } if (charsetName == null) { // determine from meta. safe first parse as UTF-8 - // look for or HTML5 - docData = Charset.forName(defaultCharset).decode(byteData).toString(); + String docData = Charset.forName(defaultCharset).decode(firstBytes).toString(); doc = parser.parseInput(docData, baseUri); + + // look for or HTML5 Elements metaElements = doc.select("meta[http-equiv=content-type], meta[charset]"); String foundCharset = null; // if not found, will keep utf-8 as best attempt for (Element meta : metaElements) { - if (meta.hasAttr("http-equiv")) { + if (meta.hasAttr("http-equiv")) foundCharset = getCharsetFromContentType(meta.attr("content")); - } - if (foundCharset == null && meta.hasAttr("charset")) { + if (foundCharset == null && meta.hasAttr("charset")) foundCharset = meta.attr("charset"); - } - if (foundCharset != null) { + if (foundCharset != null) break; - } } // look for if (foundCharset == null && doc.childNodeSize() > 0 && doc.childNode(0) instanceof XmlDeclaration) { XmlDeclaration prolog = (XmlDeclaration) doc.childNode(0); - if (prolog.name().equals("xml")) { + if (prolog.name().equals("xml")) foundCharset = prolog.attr("encoding"); - } } foundCharset = validateCharset(foundCharset); if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharset)) { // need to re-decode. (case insensitive check here to match how validate works) foundCharset = foundCharset.trim().replaceAll("[\"']", ""); charsetName = foundCharset; - byteData.rewind(); - docData = Charset.forName(foundCharset).decode(byteData).toString(); + doc = null; + } else if (!fullyRead) { doc = null; } } else { // specified by content type header (or by user on file load) Validate.notEmpty(charsetName, "Must set charset arg to character set of file to parse. Set to null to attempt to detect from HTML"); - docData = Charset.forName(charsetName).decode(byteData).toString(); } if (doc == null) { - doc = parser.parseInput(docData, baseUri); + if (charsetName == null) + charsetName = defaultCharset; + BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); + doc = parser.parseInput(reader, baseUri); doc.outputSettings().charset(charsetName); } + input.close(); return doc; } @@ -153,16 +168,17 @@ static Document parseByteData(ByteBuffer byteData, String charsetName, String ba public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) throws IOException { Validate.isTrue(maxSize >= 0, "maxSize must be 0 (unlimited) or larger"); final boolean capped = maxSize > 0; - byte[] buffer = new byte[capped && maxSize < bufferSize ? maxSize : bufferSize]; - ByteArrayOutputStream outStream = new ByteArrayOutputStream(capped ? maxSize : bufferSize); + final byte[] buffer = new byte[capped && maxSize < bufferSize ? maxSize : bufferSize]; + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(capped ? maxSize : bufferSize); + int read; int remaining = maxSize; - while (!Thread.interrupted()) { + while (true) { read = inStream.read(buffer); if (read == -1) break; if (capped) { - if (read > remaining) { + if (read >= remaining) { outStream.write(buffer, 0, remaining); break; } @@ -170,7 +186,6 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr } outStream.write(buffer, 0, read); } - return ByteBuffer.wrap(outStream.toByteArray()); } @@ -237,7 +252,7 @@ static String mimeBoundary() { return mime.toString(); } - private static String detectCharsetFromBom(ByteBuffer byteData, String charsetName) { + private static BomCharset detectCharsetFromBom(final ByteBuffer byteData, final String charsetName) { byteData.mark(); byte[] bom = new byte[4]; if (byteData.remaining() >= bom.length) { @@ -246,14 +261,24 @@ private static String detectCharsetFromBom(ByteBuffer byteData, String charsetNa } if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF || // BE bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { // LE - charsetName = "UTF-32"; // and I hope it's on your system + return new BomCharset("UTF-32", 0); // and I hope it's on your system } else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF || // BE bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) { - charsetName = "UTF-16"; // in all Javas + return new BomCharset("UTF-16", 0); // in all Javas } else if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) { - charsetName = "UTF-8"; // in all Javas - byteData.position(3); // 16 and 32 decoders consume the BOM to determine be/le; utf-8 should be consumed here + return new BomCharset("UTF-8", 3); // in all Javas + // 16 and 32 decoders consume the BOM to determine be/le; utf-8 should be consumed here + } + return null; + } + + private static class BomCharset { + private final String charset; + private final int offset; + + public BomCharset(String charset, int offset) { + this.charset = charset; + this.offset = offset; } - return charsetName; } } diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 5cb1f9fed3..21a43f65fc 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -1,20 +1,46 @@ package org.jsoup.helper; -import org.jsoup.*; +import org.jsoup.Connection; +import org.jsoup.HttpStatusException; +import org.jsoup.UncheckedIOException; +import org.jsoup.UnsupportedMimeTypeException; +import org.jsoup.internal.ConstrainableInputStream; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.jsoup.parser.TokenQueue; -import javax.net.ssl.*; -import java.io.*; -import java.net.*; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; @@ -26,7 +52,7 @@ * @see org.jsoup.Jsoup#connect(String) */ public class HttpConnection implements Connection { - public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final String CONTENT_ENCODING = "Content-Encoding"; /** * Many users would get caught by not setting a user-agent and therefore getting different responses on their desktop * vs in jsoup, which would otherwise default to {@code Java}. So by default, use a desktop UA. @@ -65,10 +91,12 @@ private static String encodeUrl(String url) { } } - private static URL encodeUrl(URL u) { + static URL encodeUrl(URL u) { try { // odd way to encode urls, but it works! - final URI uri = new URI(u.toExternalForm()); + String urlS = u.toExternalForm(); // URL external form may have spaces which is illegal in new URL() (odd asymmetry) + urlS = urlS.replaceAll(" ", "%20"); + final URI uri = new URI(urlS); return new URL(uri.toASCIIString()); } catch (Exception e) { return u; @@ -601,9 +629,11 @@ public static class Response extends HttpConnection.Base im private int statusCode; private String statusMessage; private ByteBuffer byteData; + private InputStream bodyStream; private String charset; private String contentType; private boolean executed = false; + private boolean inputStreamRead = false; private int numRedirects = 0; private Connection.Request req; @@ -701,23 +731,19 @@ else if (methodHasBody) res.charset = DataUtil.getCharsetFromContentType(res.contentType); // may be null, readInputStream deals with it if (conn.getContentLength() != 0 && req.method() != HEAD) { // -1 means unknown, chunked. sun throws an IO exception on 500 response with no content when trying to read body - InputStream bodyStream = null; - try { - bodyStream = conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream(); - if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) - bodyStream = new GZIPInputStream(bodyStream); - - res.byteData = DataUtil.readToByteBuffer(bodyStream, req.maxBodySize()); - } finally { - if (bodyStream != null) bodyStream.close(); - } + res.bodyStream = null; + res.bodyStream = conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream(); + if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) + res.bodyStream = new GZIPInputStream(res.bodyStream); + res.bodyStream = new ConstrainableInputStream(res.bodyStream, DataUtil.bufferSize, req.maxBodySize()); } else { res.byteData = DataUtil.emptyByteBuffer(); } - } finally { + } catch (IOException e){ // per Java's documentation, this is not necessary, and precludes keepalives. However in practise, // connection errors will not be released quickly enough and can cause a too many open files error. conn.disconnect(); + throw e; } res.executed = true; @@ -747,14 +773,32 @@ public String contentType() { public Document parse() throws IOException { Validate.isTrue(executed, "Request must be executed (with .execute(), .get(), or .post() before parsing response"); - Document doc = DataUtil.parseByteData(byteData, charset, url.toExternalForm(), req.parser()); - byteData.rewind(); + if (byteData != null) { // bytes have been read in to the buffer, parse that + bodyStream = new ByteArrayInputStream(byteData.array()); + inputStreamRead = false; // ok to reparse if in bytes + } + Validate.isFalse(inputStreamRead, "Input stream already read and parsed, cannot re-read."); + Document doc = DataUtil.parseInputStream(bodyStream, charset, url.toExternalForm(), req.parser()); charset = doc.outputSettings().charset().name(); // update charset from meta-equiv, possibly + // todo - disconnect here? + inputStreamRead = true; return doc; } - public String body() { + private void prepareByteData() { Validate.isTrue(executed, "Request must be executed (with .execute(), .get(), or .post() before getting response body"); + if (byteData == null) { + Validate.isFalse(inputStreamRead, "Request has already been read (with .parse())"); + try { + byteData = DataUtil.readToByteBuffer(bodyStream, req.maxBodySize()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + public String body() { + prepareByteData(); // charset gets set from header on execute, and from meta-equiv on parse. parse may not have happened yet String body; if (charset == null) @@ -766,7 +810,7 @@ public String body() { } public byte[] bodyAsBytes() { - Validate.isTrue(executed, "Request must be executed (with .execute(), .get(), or .post() before getting response body"); + prepareByteData(); return byteData.array(); } diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 27d45c9566..c0235d2cdb 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -120,7 +120,7 @@ private void copyAttributes(org.jsoup.nodes.Node source, Element el) { for (Attribute attribute : source.attributes()) { // valid xml attribute names are: ^[a-zA-Z_:][-a-zA-Z0-9_:.] String key = attribute.getKey().replaceAll("[^-a-zA-Z0-9_:.]", ""); - if (key.matches("[a-zA-Z_:]{1}[-a-zA-Z0-9_:.]*")) + if (key.matches("[a-zA-Z_:][-a-zA-Z0-9_:.]*")) el.setAttribute(key, attribute.getValue()); } } diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java new file mode 100644 index 0000000000..1b8a65dbc9 --- /dev/null +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -0,0 +1,35 @@ +package org.jsoup.internal; + +import org.jsoup.helper.Validate; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A jsoup internal class (so don't use it as there is no contract API) that enables constraints on an Input Stream, + * namely a maximum read size, and the ability to Thread.interrupt() the read. + */ +public final class ConstrainableInputStream extends BufferedInputStream { + private final boolean capped; + private int remaining; + + public ConstrainableInputStream(InputStream in, int bufferSize, int maxSize) { + super(in, bufferSize); + Validate.isTrue(maxSize >= 0); + remaining = maxSize; + capped = maxSize != 0; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (Thread.interrupted() || remaining < 0) + return -1; + + final int read = super.read(b, off, len); + if (capped) { + remaining -= read; + } + return read; + } +} diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 1c10d1f52b..1f7cc6b2b6 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -1,14 +1,15 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.DataUtil; import org.jsoup.helper.StringUtil; +import org.jsoup.helper.Validate; import org.jsoup.parser.CharacterReader; import org.jsoup.parser.Parser; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.nio.ByteBuffer; +import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.Arrays; @@ -26,6 +27,7 @@ public class Entities { private static final int empty = -1; private static final String emptyName = ""; static final int codepointRadix = 36; + private static final Charset ASCII = Charset.forName("ascii"); public enum EscapeMode { /** @@ -301,48 +303,42 @@ private static void load(EscapeMode e, String file, int size) { throw new IllegalStateException("Could not read resource " + file + ". Make sure you copy resources for " + Entities.class.getCanonicalName()); int i = 0; - try { - ByteBuffer bytes = DataUtil.readToByteBuffer(stream, 0); - String contents = Charset.forName("ascii").decode(bytes).toString(); - CharacterReader reader = new CharacterReader(contents); - - while (!reader.isEmpty()) { - // NotNestedLessLess=10913,824;1887 - - final String name = reader.consumeTo('='); - reader.advance(); - final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); - final char codeDelim = reader.current(); - reader.advance(); - final int cp2; - if (codeDelim == ',') { - cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); - reader.advance(); - } else { - cp2 = empty; - } - String indexS = reader.consumeTo('\n'); - // default git checkout on windows will add a \r there, so remove - if (indexS.charAt(indexS.length() - 1) == '\r') { - indexS = indexS.substring(0, indexS.length() - 1); - } - final int index = Integer.parseInt(indexS, codepointRadix); + BufferedReader input = new BufferedReader(new InputStreamReader(stream, ASCII)); + CharacterReader reader = new CharacterReader(input); + + while (!reader.isEmpty()) { + // NotNestedLessLess=10913,824;1887 + + final String name = reader.consumeTo('='); + reader.advance(); + final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); + final char codeDelim = reader.current(); + reader.advance(); + final int cp2; + if (codeDelim == ',') { + cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); reader.advance(); + } else { + cp2 = empty; + } + String indexS = reader.consumeTo('\n'); + // default git checkout on windows will add a \r there, so remove + if (indexS.charAt(indexS.length() - 1) == '\r') { + indexS = indexS.substring(0, indexS.length() - 1); + } + final int index = Integer.parseInt(indexS, codepointRadix); + reader.advance(); - e.nameKeys[i] = name; - e.codeVals[i] = cp1; - e.codeKeys[index] = cp1; - e.nameVals[index] = name; - - if (cp2 != empty) { - multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); - } - i++; - + e.nameKeys[i] = name; + e.codeVals[i] = cp1; + e.codeKeys[index] = cp1; + e.nameVals[index] = name; + if (cp2 != empty) { + multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); } - } catch (IOException err) { - throw new IllegalStateException("Error reading resource " + file); + i++; } + Validate.isTrue(i == size, "Unexpected count of entities loaded for " + file); } } diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 7f18619278..c3638ae862 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -1,7 +1,11 @@ package org.jsoup.parser; +import org.jsoup.UncheckedIOException; import org.jsoup.helper.Validate; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; import java.util.Arrays; import java.util.Locale; @@ -10,18 +14,51 @@ */ public final class CharacterReader { static final char EOF = (char) -1; - private static final int maxCacheLen = 12; - - private final char[] input; - private final int length; - private int pos = 0; - private int mark = 0; + private static final int maxStringCacheLen = 12; + private static final int maxBufferLen = 1024 * 32; + private static final int readAheadLimit = (int) (maxBufferLen * 0.75); + + private final char[] charBuf; + private final Reader reader; + private int bufLength; + private int bufSplitPoint; + private int bufPos; + private int readerPos; + private int bufMark; private final String[] stringCache = new String[512]; // holds reused strings in this doc, to lessen garbage - public CharacterReader(String input) { + public CharacterReader(Reader input, int sz) { Validate.notNull(input); - this.input = input.toCharArray(); - this.length = this.input.length; + Validate.isTrue(input.markSupported()); + reader = input; + charBuf = new char[sz > maxBufferLen ? maxBufferLen : sz]; + bufferUp(); + } + + public CharacterReader(Reader input) { + this(input, maxBufferLen); + } + + public CharacterReader(String input) { + this(new StringReader(input), input.length()); + } + + private void bufferUp() { + if (bufPos < bufSplitPoint) + return; + + try { + readerPos += bufPos; + reader.skip(bufPos); + reader.mark(maxBufferLen); + bufLength = reader.read(charBuf); + reader.reset(); + bufPos = 0; + bufMark = 0; + bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -29,7 +66,7 @@ public CharacterReader(String input) { * @return current position */ public int pos() { - return pos; + return readerPos + bufPos; } /** @@ -37,7 +74,7 @@ public int pos() { * @return true if nothing left to read. */ public boolean isEmpty() { - return pos >= length; + return bufPos >= bufLength; } /** @@ -45,36 +82,34 @@ public boolean isEmpty() { * @return char */ public char current() { - return pos >= length ? EOF : input[pos]; + bufferUp(); + return isEmpty() ? EOF : charBuf[bufPos]; } char consume() { - char val = pos >= length ? EOF : input[pos]; - pos++; + bufferUp(); + char val = isEmpty() ? EOF : charBuf[bufPos]; + bufPos++; return val; } void unconsume() { - pos--; + bufPos--; } /** * Moves the current position by one. */ public void advance() { - pos++; + bufPos++; } void mark() { - mark = pos; + bufMark = bufPos; } void rewindToMark() { - pos = mark; - } - - String consumeAsString() { - return new String(input, pos++, 1); + bufPos = bufMark; } /** @@ -84,9 +119,10 @@ String consumeAsString() { */ int nextIndexOf(char c) { // doesn't handle scanning for surrogates - for (int i = pos; i < length; i++) { - if (c == input[i]) - return i - pos; + bufferUp(); + for (int i = bufPos; i < bufLength; i++) { + if (c == charBuf[i]) + return i - bufPos; } return -1; } @@ -98,18 +134,19 @@ int nextIndexOf(char c) { * @return offset between current position and next instance of target. -1 if not found. */ int nextIndexOf(CharSequence seq) { + bufferUp(); // doesn't handle scanning for surrogates char startChar = seq.charAt(0); - for (int offset = pos; offset < length; offset++) { + for (int offset = bufPos; offset < bufLength; offset++) { // scan to first instance of startchar: - if (startChar != input[offset]) - while(++offset < length && startChar != input[offset]) { /* empty */ } + if (startChar != charBuf[offset]) + while(++offset < bufLength && startChar != charBuf[offset]) { /* empty */ } int i = offset + 1; int last = i + seq.length()-1; - if (offset < length && last <= length) { - for (int j = 1; i < last && seq.charAt(j) == input[i]; i++, j++) { /* empty */ } + if (offset < bufLength && last <= bufLength) { + for (int j = 1; i < last && seq.charAt(j) == charBuf[i]; i++, j++) { /* empty */ } if (i == last) // found full sequence - return offset - pos; + return offset - bufPos; } } return -1; @@ -123,8 +160,8 @@ int nextIndexOf(CharSequence seq) { public String consumeTo(char c) { int offset = nextIndexOf(c); if (offset != -1) { - String consumed = cacheString(pos, offset); - pos += offset; + String consumed = cacheString(charBuf, stringCache, bufPos, offset); + bufPos += offset; return consumed; } else { return consumeToEnd(); @@ -134,8 +171,8 @@ public String consumeTo(char c) { String consumeTo(String seq) { int offset = nextIndexOf(seq); if (offset != -1) { - String consumed = cacheString(pos, offset); - pos += offset; + String consumed = cacheString(charBuf, stringCache, bufPos, offset); + bufPos += offset; return consumed; } else { return consumeToEnd(); @@ -148,154 +185,165 @@ String consumeTo(String seq) { * @return characters read up to the matched delimiter. */ public String consumeToAny(final char... chars) { - final int start = pos; - final int remaining = length; - final char[] val = input; + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; - OUTER: while (pos < remaining) { + OUTER: while (bufPos < remaining) { for (char c : chars) { - if (val[pos] == c) + if (val[bufPos] == c) break OUTER; } - pos++; + bufPos++; } - return pos > start ? cacheString(start, pos-start) : ""; + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; } String consumeToAnySorted(final char... chars) { - final int start = pos; - final int remaining = length; - final char[] val = input; + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; - while (pos < remaining) { - if (Arrays.binarySearch(chars, val[pos]) >= 0) + while (bufPos < remaining) { + if (Arrays.binarySearch(chars, val[bufPos]) >= 0) break; - pos++; + bufPos++; } - return pos > start ? cacheString(start, pos-start) : ""; + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; } String consumeData() { // &, <, null - final int start = pos; - final int remaining = length; - final char[] val = input; + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; - while (pos < remaining) { - final char c = val[pos]; + while (bufPos < remaining) { + final char c = val[bufPos]; if (c == '&'|| c == '<' || c == TokeniserState.nullChar) break; - pos++; + bufPos++; } - return pos > start ? cacheString(start, pos-start) : ""; + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; } String consumeTagName() { // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar - final int start = pos; - final int remaining = length; - final char[] val = input; + bufferUp(); + final int start = bufPos; + final int remaining = bufLength; + final char[] val = charBuf; - while (pos < remaining) { - final char c = val[pos]; + while (bufPos < remaining) { + final char c = val[bufPos]; if (c == '\t'|| c == '\n'|| c == '\r'|| c == '\f'|| c == ' '|| c == '/'|| c == '>'|| c == TokeniserState.nullChar) break; - pos++; + bufPos++; } - return pos > start ? cacheString(start, pos-start) : ""; + return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; } String consumeToEnd() { - String data = cacheString(pos, length-pos); - pos = length; + bufferUp(); + String data = cacheString(charBuf, stringCache, bufPos, bufLength - bufPos); + bufPos = bufLength; return data; } String consumeLetterSequence() { - int start = pos; - while (pos < length) { - char c = input[pos]; + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) - pos++; + bufPos++; else break; } - return cacheString(start, pos - start); + return cacheString(charBuf, stringCache, start, bufPos - start); } String consumeLetterThenDigitSequence() { - int start = pos; - while (pos < length) { - char c = input[pos]; + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) - pos++; + bufPos++; else break; } while (!isEmpty()) { - char c = input[pos]; + char c = charBuf[bufPos]; if (c >= '0' && c <= '9') - pos++; + bufPos++; else break; } - return cacheString(start, pos - start); + return cacheString(charBuf, stringCache, start, bufPos - start); } String consumeHexSequence() { - int start = pos; - while (pos < length) { - char c = input[pos]; + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) - pos++; + bufPos++; else break; } - return cacheString(start, pos - start); + return cacheString(charBuf, stringCache, start, bufPos - start); } String consumeDigitSequence() { - int start = pos; - while (pos < length) { - char c = input[pos]; + bufferUp(); + int start = bufPos; + while (bufPos < bufLength) { + char c = charBuf[bufPos]; if (c >= '0' && c <= '9') - pos++; + bufPos++; else break; } - return cacheString(start, pos - start); + return cacheString(charBuf, stringCache, start, bufPos - start); } boolean matches(char c) { - return !isEmpty() && input[pos] == c; + return !isEmpty() && charBuf[bufPos] == c; } boolean matches(String seq) { + bufferUp(); int scanLength = seq.length(); - if (scanLength > length - pos) + if (scanLength > bufLength - bufPos) return false; for (int offset = 0; offset < scanLength; offset++) - if (seq.charAt(offset) != input[pos+offset]) + if (seq.charAt(offset) != charBuf[bufPos +offset]) return false; return true; } boolean matchesIgnoreCase(String seq) { + bufferUp(); int scanLength = seq.length(); - if (scanLength > length - pos) + if (scanLength > bufLength - bufPos) return false; for (int offset = 0; offset < scanLength; offset++) { char upScan = Character.toUpperCase(seq.charAt(offset)); - char upTarget = Character.toUpperCase(input[pos + offset]); + char upTarget = Character.toUpperCase(charBuf[bufPos + offset]); if (upScan != upTarget) return false; } @@ -306,7 +354,8 @@ boolean matchesAny(char... seq) { if (isEmpty()) return false; - char c = input[pos]; + bufferUp(); + char c = charBuf[bufPos]; for (char seek : seq) { if (seek == c) return true; @@ -315,26 +364,28 @@ boolean matchesAny(char... seq) { } boolean matchesAnySorted(char[] seq) { - return !isEmpty() && Arrays.binarySearch(seq, input[pos]) >= 0; + bufferUp(); + return !isEmpty() && Arrays.binarySearch(seq, charBuf[bufPos]) >= 0; } boolean matchesLetter() { if (isEmpty()) return false; - char c = input[pos]; + char c = charBuf[bufPos]; return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c); } boolean matchesDigit() { if (isEmpty()) return false; - char c = input[pos]; + char c = charBuf[bufPos]; return (c >= '0' && c <= '9'); } boolean matchConsume(String seq) { + bufferUp(); if (matches(seq)) { - pos += seq.length(); + bufPos += seq.length(); return true; } else { return false; @@ -343,7 +394,7 @@ boolean matchConsume(String seq) { boolean matchConsumeIgnoreCase(String seq) { if (matchesIgnoreCase(seq)) { - pos += seq.length(); + bufPos += seq.length(); return true; } else { return false; @@ -359,7 +410,7 @@ boolean containsIgnoreCase(String seq) { @Override public String toString() { - return new String(input, pos, length - pos); + return new String(charBuf, bufPos, bufLength - bufPos); } /** @@ -369,34 +420,31 @@ public String toString() { * That saves both having to create objects as hash keys, and running through the entry list, at the expense of * some more duplicates. */ - private String cacheString(final int start, final int count) { - final char[] val = input; - final String[] cache = stringCache; - + private static String cacheString(final char[] charBuf, final String[] stringCache, final int start, final int count) { // limit (no cache): - if (count > maxCacheLen) - return new String(val, start, count); + if (count > maxStringCacheLen) + return new String(charBuf, start, count); // calculate hash: int hash = 0; int offset = start; for (int i = 0; i < count; i++) { - hash = 31 * hash + val[offset++]; + hash = 31 * hash + charBuf[offset++]; } // get from cache - final int index = hash & cache.length - 1; - String cached = cache[index]; + final int index = hash & stringCache.length - 1; + String cached = stringCache[index]; if (cached == null) { // miss, add - cached = new String(val, start, count); - cache[index] = cached; + cached = new String(charBuf, start, count); + stringCache[index] = cached; } else { // hashcode hit, check equality - if (rangeEquals(start, count, cached)) { // hit + if (rangeEquals(charBuf, start, count, cached)) { // hit return cached; } else { // hashcode conflict - cached = new String(val, start, count); - cache[index] = cached; // update the cache, as recently used strings are more likely to show up again + cached = new String(charBuf, start, count); + stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again } } return cached; @@ -405,17 +453,21 @@ private String cacheString(final int start, final int count) { /** * Check if the value of the provided range equals the string. */ - boolean rangeEquals(final int start, int count, final String cached) { + static boolean rangeEquals(final char[] charBuf, final int start, int count, final String cached) { if (count == cached.length()) { - char one[] = input; int i = start; int j = 0; while (count-- != 0) { - if (one[i++] != cached.charAt(j++)) + if (charBuf[i++] != cached.charAt(j++)) return false; } return true; } return false; } + + // just used for testing + boolean rangeEquals(final int start, final int count, final String cached) { + return rangeEquals(charBuf, start, count, cached); + } } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 4f0d0fc567..3c8e15a898 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -11,6 +11,8 @@ import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; +import java.io.Reader; +import java.io.StringReader; import java.util.ArrayList; import java.util.List; @@ -56,7 +58,7 @@ ParseSettings defaultSettings() { } @Override - protected void initialiseParse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { + protected void initialiseParse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { super.initialiseParse(input, baseUri, errors, settings); // this is a bit mucky. todo - probably just create new parser objects to ensure all reset. @@ -77,7 +79,7 @@ protected void initialiseParse(String input, String baseUri, ParseErrorList erro List parseFragment(String inputFragment, Element context, String baseUri, ParseErrorList errors, ParseSettings settings) { // context may be null state = HtmlTreeBuilderState.Initial; - initialiseParse(inputFragment, baseUri, errors, settings); + initialiseParse(new StringReader(inputFragment), baseUri, errors, settings); contextElement = context; fragmentParsing = true; Element root = null; diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index 0751c22999..f3e99e1d80 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -4,6 +4,8 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import java.io.Reader; +import java.io.StringReader; import java.util.List; /** @@ -29,7 +31,12 @@ public Parser(TreeBuilder treeBuilder) { public Document parseInput(String html, String baseUri) { errors = isTrackErrors() ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking(); - return treeBuilder.parse(html, baseUri, errors, settings); + return treeBuilder.parse(new StringReader(html), baseUri, errors, settings); + } + + public Document parseInput(Reader inputHtml, String baseUri) { + errors = isTrackErrors() ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking(); + return treeBuilder.parse(inputHtml, baseUri, errors, settings); } // gets & sets @@ -97,7 +104,7 @@ public ParseSettings settings() { */ public static Document parse(String html, String baseUri) { TreeBuilder treeBuilder = new HtmlTreeBuilder(); - return treeBuilder.parse(html, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); + return treeBuilder.parse(new StringReader(html), baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); } /** diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 6a97238d49..3b02501abc 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -1678,7 +1678,7 @@ private static void readData(Tokeniser t, CharacterReader r, TokeniserState curr t.emit(new Token.EOF()); break; default: - String data = r.consumeToAny('<', nullChar); + String data = r.consumeToAny('<', nullChar); // todo - why hunt for null here? Just consumeTo'<'? t.emit(data); break; } diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 6ff1bad84b..6833043839 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -5,6 +5,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import java.io.Reader; import java.util.ArrayList; /** @@ -25,7 +26,7 @@ abstract class TreeBuilder { abstract ParseSettings defaultSettings(); - protected void initialiseParse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { + protected void initialiseParse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { Validate.notNull(input, "String input must not be null"); Validate.notNull(baseUri, "BaseURI must not be null"); @@ -39,7 +40,7 @@ protected void initialiseParse(String input, String baseUri, ParseErrorList erro this.baseUri = baseUri; } - Document parse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { + Document parse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { initialiseParse(input, baseUri, errors, settings); runParser(); return doc; diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index cfcb1a34c6..082b5856aa 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -4,6 +4,8 @@ import org.jsoup.helper.Validate; import org.jsoup.nodes.*; +import java.io.Reader; +import java.io.StringReader; import java.util.List; /** @@ -18,12 +20,16 @@ ParseSettings defaultSettings() { return ParseSettings.preserveCase; } - Document parse(String input, String baseUri) { + Document parse(Reader input, String baseUri) { return parse(input, baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase); } + Document parse(String input, String baseUri) { + return parse(new StringReader(input), baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase); + } + @Override - protected void initialiseParse(String input, String baseUri, ParseErrorList errors, ParseSettings settings) { + protected void initialiseParse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { super.initialiseParse(input, baseUri, errors, settings); stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); @@ -130,7 +136,7 @@ private void popStackToClose(Token.EndTag endTag) { } List parseFragment(String inputFragment, String baseUri, ParseErrorList errors, ParseSettings settings) { - initialiseParse(inputFragment, baseUri, errors, settings); + initialiseParse(new StringReader(inputFragment), baseUri, errors, settings); runParser(); return doc.childNodes(); } diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index bb5a98ece0..e7ee75b3de 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -286,8 +286,8 @@ private void indexEquals() { } //pseudo selectors :first-child, :last-child, :nth-child, ... - private static final Pattern NTH_AB = Pattern.compile("((\\+|-)?(\\d+)?)n(\\s*(\\+|-)?\\s*\\d+)?", Pattern.CASE_INSENSITIVE); - private static final Pattern NTH_B = Pattern.compile("(\\+|-)?(\\d+)"); + private static final Pattern NTH_AB = Pattern.compile("(([+-])?(\\d+)?)n(\\s*([+-])?\\s*\\d+)?", Pattern.CASE_INSENSITIVE); + private static final Pattern NTH_B = Pattern.compile("([+-])?(\\d+)"); private void cssNthChild(boolean backwards, boolean ofType) { String argS = normalize(tq.chompTo(")")); diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 94003b4912..70185d3cfe 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -1,19 +1,22 @@ package org.jsoup.helper; -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; - import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.junit.Test; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import static org.jsoup.integration.ParseTest.getFile; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class DataUtilTest { @Test @@ -34,17 +37,28 @@ public void testCharset() { assertEquals("UTF-8", DataUtil.getCharsetFromContentType("text/html; charset='UTF-8'")); } - @Test public void discardsSpuriousByteOrderMark() { + private InputStream stream(String data) { + return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); + } + + private InputStream stream(String data, String charset) { + try { + return new ByteArrayInputStream(data.getBytes(charset)); + } catch (UnsupportedEncodingException e) { + fail(); + } + return null; + } + + @Test public void discardsSpuriousByteOrderMark() throws IOException { String html = "\uFEFFOne Two"; - ByteBuffer buffer = Charset.forName("UTF-8").encode(html); - Document doc = DataUtil.parseByteData(buffer, "UTF-8", "http://foo.com/", Parser.htmlParser()); + Document doc = DataUtil.parseInputStream(stream(html), "UTF-8", "http://foo.com/", Parser.htmlParser()); assertEquals("One", doc.head().text()); } - @Test public void discardsSpuriousByteOrderMarkWhenNoCharsetSet() { + @Test public void discardsSpuriousByteOrderMarkWhenNoCharsetSet() throws IOException { String html = "\uFEFFOne Two"; - ByteBuffer buffer = Charset.forName("UTF-8").encode(html); - Document doc = DataUtil.parseByteData(buffer, null, "http://foo.com/", Parser.htmlParser()); + Document doc = DataUtil.parseInputStream(stream(html), null, "http://foo.com/", Parser.htmlParser()); assertEquals("One", doc.head().text()); assertEquals("UTF-8", doc.outputSettings().charset().displayName()); } @@ -81,46 +95,41 @@ public void generatesMimeBoundaries() { } @Test - public void wrongMetaCharsetFallback() { - try { - final byte[] input = " ".getBytes("UTF-8"); - final ByteBuffer inBuffer = ByteBuffer.wrap(input); - - Document doc = DataUtil.parseByteData(inBuffer, null, "http://example.com", Parser.htmlParser()); - - final String expected = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - - assertEquals(expected, doc.toString()); - } catch( UnsupportedEncodingException ex ) { - fail(ex.getMessage()); - } + public void wrongMetaCharsetFallback() throws IOException { + String html = " "; + + Document doc = DataUtil.parseInputStream(stream(html), null, "http://example.com", Parser.htmlParser()); + + final String expected = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + assertEquals(expected, doc.toString()); } @Test public void secondMetaElementWithContentTypeContainsCharsetParameter() throws Exception { - ByteBuffer inBuffer = ByteBuffer.wrap(("" + + String html = "" + "" + "" + - " 한국어").getBytes("euc-kr")); + " 한국어"; - Document doc = DataUtil.parseByteData(inBuffer, null, "http://example.com", Parser.htmlParser()); + Document doc = DataUtil.parseInputStream(stream(html, "euc-kr"), null, "http://example.com", Parser.htmlParser()); assertEquals("한국어", doc.body().text()); } @Test public void firstMetaElementWithCharsetShouldBeUsedForDecoding() throws Exception { - ByteBuffer inBuffer = ByteBuffer.wrap(("" + + String html = "" + "" + "" + - " Übergrößenträger").getBytes("iso-8859-1")); + " Übergrößenträger"; - Document doc = DataUtil.parseByteData(inBuffer, null, "http://example.com", Parser.htmlParser()); + Document doc = DataUtil.parseInputStream(stream(html, "iso-8859-1"), null, "http://example.com", Parser.htmlParser()); assertEquals("Übergrößenträger", doc.body().text()); } diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 5e5cab33b0..ef9042f6ed 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -1,18 +1,25 @@ package org.jsoup.helper; -import static org.junit.Assert.*; - +import org.jsoup.Connection; import org.jsoup.MultiLocaleRule; import org.jsoup.MultiLocaleRule.MultiLocaleTest; import org.jsoup.integration.ParseTest; import org.junit.Rule; import org.junit.Test; -import org.jsoup.Connection; import java.io.IOException; -import java.util.*; -import java.net.URL; import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class HttpConnectionTest { /* most actual network http connection tests are in integration */ @@ -192,4 +199,10 @@ public class HttpConnectionTest { con.requestBody("foo"); assertEquals("foo", con.request().requestBody()); } + + @Test public void encodeUrl() throws MalformedURLException { + URL url1 = new URL("http://test.com/?q=white space"); + URL url2 = HttpConnection.encodeUrl(url1); + assertEquals("http://test.com/?q=white%20space", url2.toExternalForm()); + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 5b81628f0b..993be8c5f5 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -32,6 +32,7 @@ @author Jonathan Hedley, jonathan@hedley.net */ @Ignore // ignored by default so tests don't require network access. comment out to enable. +// todo: rebuild these into a local Jetty test server, so not reliant on the vagaries of the internet. public class UrlConnectTest { private static final String WEBSITE_WITH_INVALID_CERTIFICATE = "https://certs.cac.washington.edu/CAtest/"; private static final String WEBSITE_WITH_SNI = "https://jsoup.org/"; @@ -411,16 +412,9 @@ public void maxBodySize() throws IOException { Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute(); - int actualString = 280735; - assertEquals(actualString, defaultRes.body().length()); - assertEquals(50 * 1024, smallRes.body().length()); - assertEquals(200 * 1024, mediumRes.body().length()); - assertEquals(actualString, largeRes.body().length()); - assertEquals(actualString, unlimitedRes.body().length()); - int actualDocText = 269541; assertEquals(actualDocText, defaultRes.parse().text().length()); - assertEquals(49165, smallRes.parse().text().length()); + assertEquals(47200, smallRes.parse().text().length()); assertEquals(196577, mediumRes.parse().text().length()); assertEquals(actualDocText, largeRes.parse().text().length()); assertEquals(actualDocText, unlimitedRes.parse().text().length()); @@ -703,7 +697,7 @@ public void canSpecifyResponseCharset() throws IOException { final Document doc1 = res1.parse(); assertEquals("windows-1252", doc1.charset().displayName()); // but determined at parse time assertEquals("Cost is €100", doc1.select("p").text()); - assertTrue(res1.body().contains("€")); + assertTrue(doc1.text().contains("€")); // no meta, no override Connection.Response res2 = Jsoup.connect(noCharsetUrl).execute(); @@ -711,7 +705,7 @@ public void canSpecifyResponseCharset() throws IOException { final Document doc2 = res2.parse(); assertEquals("UTF-8", doc2.charset().displayName()); // so defaults to utf-8 assertEquals("Cost is �100", doc2.select("p").text()); - assertTrue(res2.body().contains("�")); + assertTrue(doc2.text().contains("�")); // no meta, let's override Connection.Response res3 = Jsoup.connect(noCharsetUrl).execute(); @@ -721,7 +715,7 @@ public void canSpecifyResponseCharset() throws IOException { final Document doc3 = res3.parse(); assertEquals("windows-1252", doc3.charset().displayName()); // from override assertEquals("Cost is €100", doc3.select("p").text()); - assertTrue(res3.body().contains("€")); + assertTrue(doc3.text().contains("€")); } @Test @@ -741,6 +735,14 @@ public void handlesUnescapedRedirects() throws IOException { // if we didn't notice it was utf8, would look like: Location: /tools/test💩.html } + @Test public void handlesEscapesInRedirecct() throws IOException { + Document doc = Jsoup.connect("http://infohound.net/tools/302-escaped.pl").get(); + assertEquals("http://infohound.net/tools/q.pl?q=one%20two", doc.location()); + + doc = Jsoup.connect("http://infohound.net/tools/302-white.pl").get(); + assertEquals("http://infohound.net/tools/q.pl?q=one%20two", doc.location()); + } + @Test public void handlesUt8fInUrl() throws IOException { String url = "http://direct.infohound.net/tools/test\uD83D\uDCA9.html"; @@ -767,18 +769,19 @@ public void inWildUtfRedirect2() throws IOException { Connection.Response res = Jsoup.connect("https://ssl.souq.com/sa-en/2724288604627/s").execute(); Document doc = res.parse(); assertEquals( - "http://saudi.souq.com/sa-en/%D8%AE%D8%B2%D9%86%D8%A9-%D8%A2%D9%85%D9%86%D8%A9-3-%D8%B7%D8%A8%D9%82%D8%A7%D8%AA-%D8%A8%D9%86%D8%B8%D8%A7%D9%85-%D9%82%D9%81%D9%84-%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-bsd11523-6831477/i/?ctype=dsrch", + "https://saudi.souq.com/sa-en/%D8%AE%D8%B2%D9%86%D8%A9-%D8%A2%D9%85%D9%86%D8%A9-3-%D8%B7%D8%A8%D9%82%D8%A7%D8%AA-%D8%A8%D9%86%D8%B8%D8%A7%D9%85-%D9%82%D9%81%D9%84-%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-bsd11523-6831477/i/?ctype=dsrch", doc.location() ); } - @Test public void canInterruptRead() throws IOException, InterruptedException { + @Test public void canInterruptBodyStringRead() throws IOException, InterruptedException { + // todo - implement in interruptable channels, so it's immediate final String[] body = new String[1]; Thread runner = new Thread(new Runnable() { public void run() { try { Connection.Response res = Jsoup.connect("http://jsscxml.org/serverload.stream") - .timeout(10 * 1000) + .timeout(15 * 1000) .execute(); body[0] = res.body(); } catch (IOException e) { @@ -789,7 +792,33 @@ public void run() { }); runner.start(); - Thread.sleep(1000 * 5); + Thread.sleep(1000 * 7); + runner.interrupt(); + assertTrue(runner.isInterrupted()); + runner.join(); + + assertTrue(body[0].length() > 0); + } + + @Test public void canInterruptDocumentRead() throws IOException, InterruptedException { + // todo - implement in interruptable channels, so it's immediate + final String[] body = new String[1]; + Thread runner = new Thread(new Runnable() { + public void run() { + try { + Connection.Response res = Jsoup.connect("http://jsscxml.org/serverload.stream") + .timeout(15 * 1000) + .execute(); + body[0] = res.parse().text(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + }); + + runner.start(); + Thread.sleep(1000 * 7); runner.interrupt(); assertTrue(runner.isInterrupted()); runner.join(); @@ -821,4 +850,28 @@ public void run() { doc = Jsoup.connect("http://mov-world.net/archiv/TV/A/%23No.Title/").get(); assertEquals("Index of /archiv/TV/A/%23No.Title", doc.title()); } + + @Test(expected=IllegalArgumentException.class) public void bodyAfterParseThrowsValidationError() throws IOException { + Connection.Response res = Jsoup.connect(echoURL).execute(); + Document doc = res.parse(); + String body = res.body(); + } + + @Test public void bodyAndBytesAvailableBeforeParse() throws IOException { + Connection.Response res = Jsoup.connect(echoURL).execute(); + String body = res.body(); + assertTrue(body.contains("Environment")); + byte[] bytes = res.bodyAsBytes(); + assertTrue(bytes.length > 100); + + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + } + + @Test(expected=IllegalArgumentException.class) public void parseParseThrowsValidates() throws IOException { + Connection.Response res = Jsoup.connect(echoURL).execute(); + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + Document doc2 = res.parse(); + } } diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 1a09ce2257..2a0084e58e 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -165,6 +165,7 @@ public class CharacterReaderTest { assertFalse(r.matches("ne Two Three Four")); assertEquals("ne Two Three", r.consumeToEnd()); assertFalse(r.matches("ne")); + assertTrue(r.isEmpty()); } @Test @@ -244,5 +245,16 @@ public void rangeEquals() { assertFalse(r.rangeEquals(18, 5, "CHIKE")); } + @Test + public void empty() { + CharacterReader r = new CharacterReader("One"); + assertTrue(r.matchConsume("One")); + assertTrue(r.isEmpty()); + + r = new CharacterReader("Two"); + String two = r.consumeToEnd(); + assertEquals("Two", two); + } + } From 416ad294fb97f594c05dda7da53733cea59ab0ce Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 25 Jun 2017 00:58:53 -0600 Subject: [PATCH 116/774] Case-insensitive attribute setter (#903) * Add failing test for updating attribute using differently-cased key * Do case-insensitive key comparison when replacing attributes --- src/main/java/org/jsoup/nodes/Attributes.java | 22 +++++++++++++--- src/main/java/org/jsoup/nodes/Node.java | 5 ++-- src/test/java/org/jsoup/nodes/NodeTest.java | 25 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index ffe77ab235..b014941c81 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -56,19 +56,24 @@ public String get(String key) { * @return the first matching attribute value if set; or empty string if not set. */ public String getIgnoreCase(String key) { + Attribute attr = getAttributeIgnoreCase(key); + return attr != null ? attr.getValue() : ""; + } + + private Attribute getAttributeIgnoreCase(String key) { Validate.notEmpty(key); if (attributes == null) - return ""; + return null; Attribute attr = attributes.get(key); if (attr != null) - return attr.getValue(); + return attr; for (String attrKey : attributes.keySet()) { if (attrKey.equalsIgnoreCase(key)) - return attributes.get(attrKey).getValue(); + return attributes.get(attrKey); } - return ""; + return null; } /** @@ -81,6 +86,15 @@ public void put(String key, String value) { put(attr); } + void putIgnoreCase(String key, String value) { + Attribute oldAttr = getAttributeIgnoreCase(key); + if (oldAttr != null && !oldAttr.getKey().equals(key)) { + attributes.remove(oldAttr.getKey()); + } + + put(key, value); + } + /** Set a new boolean attribute, remove attribute if value is false. @param key attribute key diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 2fa56c02b0..b82710ee08 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -96,13 +96,14 @@ public Attributes attributes() { } /** - * Set an attribute (key=value). If the attribute already exists, it is replaced. + * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is + * case insensitive. * @param attributeKey The attribute key. * @param attributeValue The attribute value. * @return this (for chaining) */ public Node attr(String attributeKey, String attributeValue) { - attributes.put(attributeKey, attributeValue); + attributes.putIgnoreCase(attributeKey, attributeValue); return this; } diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 7358c3888b..5e18338b08 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -289,4 +289,29 @@ public void tail(Node node, int depth) { assertTrue(el.text().equals("None")); assertTrue(elClone.text().equals("Text")); } + + @Test public void changingAttributeValueShouldReplaceExistingAttributeCaseInsensitive() { + Document document = Jsoup.parse(""); + Element inputElement = document.select("#foo").first(); + + inputElement.attr("value","bar"); + + assertEquals(singletonAttributes("value", "bar"), getAttributesCaseInsensitive(inputElement, "value")); + } + + private Attributes getAttributesCaseInsensitive(Element element, String attributeName) { + Attributes matches = new Attributes(); + for (Attribute attribute : element.attributes()) { + if (attribute.getKey().equalsIgnoreCase(attributeName)) { + matches.put(attribute); + } + } + return matches; + } + + private Attributes singletonAttributes(String key, String value) { + Attributes attributes = new Attributes(); + attributes.put(key, value); + return attributes; + } } From d51f6539c6245847f4296230077faade0e5d6973 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 25 Jun 2017 00:01:32 -0700 Subject: [PATCH 117/774] Changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 9109fc1e44..5b9478142b 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,9 @@ jsoup changelog which could lead to an incorrect DOM. + * Bugfix: attributes with the same name but different case would be incorrectly treated as different attributes. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: From 35bceca331e07938a9c4cd62d68e08740cd39575 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 25 Jun 2017 10:44:55 -0700 Subject: [PATCH 118/774] Cleaned up self-closing ack and error track Fixes #868 --- CHANGES | 3 +++ .../org/jsoup/parser/HtmlTreeBuilder.java | 9 +++---- .../jsoup/parser/HtmlTreeBuilderState.java | 3 --- src/main/java/org/jsoup/parser/Tokeniser.java | 14 +--------- .../java/org/jsoup/parser/XmlTreeBuilder.java | 1 - .../java/org/jsoup/parser/HtmlParserTest.java | 26 ++++++++++++++++++- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index 5b9478142b..670809fd46 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ jsoup changelog * Bugfix: attributes with the same name but different case would be incorrectly treated as different attributes. + * Bugfix: self-closing tags for known empty elements were incorrectly treated as errors. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 3c8e15a898..3fe076ad1c 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -224,12 +224,11 @@ Element insertEmpty(Token.StartTag startTag) { insertNode(el); if (startTag.isSelfClosing()) { if (tag.isKnownTag()) { - if (tag.isSelfClosing()) tokeniser.acknowledgeSelfClosingFlag(); // if not acked, promulagates error - } else { - // unknown tag, remember this is self closing for output - tag.setSelfClosing(); - tokeniser.acknowledgeSelfClosingFlag(); // not an distinct error + if (!tag.isEmpty()) + tokeniser.error("Tag cannot be self closing; not a void tag"); } + else // unknown tag, remember this is self closing for output + tag.setSelfClosing(); } return el; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 9a118ebffc..e8373838da 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -466,7 +466,6 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (tb.getFormElement() != null) return false; - tb.tokeniser.acknowledgeSelfClosingFlag(); tb.processStartTag("form"); if (startTag.attributes.hasKey("action")) { Element form = tb.getFormElement(); @@ -540,12 +539,10 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.reconstructFormattingElements(); // todo: handle A start tag whose tag name is "math" (i.e. foreign, mathml) tb.insert(startTag); - tb.tokeniser.acknowledgeSelfClosingFlag(); } else if (name.equals("svg")) { tb.reconstructFormattingElements(); // todo: handle A start tag whose tag name is "svg" (xlink, svg) tb.insert(startTag); - tb.tokeniser.acknowledgeSelfClosingFlag(); } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { tb.error(this); return false; diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 1526c2ad69..b0479400a7 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -33,7 +33,6 @@ final class Tokeniser { Token.Doctype doctypePending = new Token.Doctype(); // doctype building up Token.Comment commentPending = new Token.Comment(); // comment building up private String lastStartTag; // the last start tag emitted, to test appropriate end tag - private boolean selfClosingFlagAcknowledged = true; Tokeniser(CharacterReader reader, ParseErrorList errors) { this.reader = reader; @@ -41,11 +40,6 @@ final class Tokeniser { } Token read() { - if (!selfClosingFlagAcknowledged) { - error("Self closing flag not acknowledged"); - selfClosingFlagAcknowledged = true; - } - while (!isEmitPending) state.read(this, reader); @@ -74,8 +68,6 @@ void emit(Token token) { if (token.type == Token.TokenType.StartTag) { Token.StartTag startTag = (Token.StartTag) token; lastStartTag = startTag.tagName; - if (startTag.selfClosing) - selfClosingFlagAcknowledged = false; } else if (token.type == Token.TokenType.EndTag) { Token.EndTag endTag = (Token.EndTag) token; if (endTag.attributes != null) @@ -122,10 +114,6 @@ void advanceTransition(TokeniserState state) { this.state = state; } - void acknowledgeSelfClosingFlag() { - selfClosingFlagAcknowledged = true; - } - final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays final private int[] multipointHolder = new int[2]; int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { @@ -252,7 +240,7 @@ private void characterReferenceError(String message) { errors.add(new ParseError(reader.pos(), "Invalid character reference: %s", message)); } - private void error(String errorMsg) { + void error(String errorMsg) { if (errors.canAddError()) errors.add(new ParseError(reader.pos(), errorMsg)); } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 082b5856aa..0507243ddd 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -72,7 +72,6 @@ Element insert(Token.StartTag startTag) { Element el = new Element(tag, baseUri, settings.normalizeAttributes(startTag.attributes)); insertNode(el); if (startTag.isSelfClosing()) { - tokeniser.acknowledgeSelfClosingFlag(); if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above. tag.setSelfClosing(); } else { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index d228253c85..6c69edbe55 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -5,6 +5,7 @@ import org.jsoup.helper.StringUtil; import org.jsoup.integration.ParseTest; import org.jsoup.nodes.*; +import org.jsoup.safety.Whitelist; import org.jsoup.select.Elements; import org.junit.Test; @@ -13,6 +14,7 @@ import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** @@ -694,7 +696,7 @@ public class HtmlParserTest { assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); assertEquals("36: Invalid character reference: invalid named referenece 'arrgh'", errors.get(2).toString()); - assertEquals("50: Self closing flag not acknowledged", errors.get(3).toString()); + assertEquals("50: Tag cannot be self closing; not a void tag", errors.get(3).toString()); assertEquals("61: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(4).toString()); } @@ -960,6 +962,28 @@ public void testInvalidTableContents() throws IOException { parser.settings(ParseSettings.preserveCase); Document doc = parser.parseInput(html, ""); assertEquals(" A B ", StringUtil.normaliseWhitespace(doc.body().html())); + } + + @Test public void selfClosingVoidIsNotAnError() { + String html = "

test
test

"; + Parser parser = Parser.htmlParser().setTrackErrors(5); + parser.parseInput(html, ""); + assertEquals(0, parser.getErrors().size()); + + assertTrue(Jsoup.isValid(html, Whitelist.basic())); + String clean = Jsoup.clean(html, Whitelist.basic()); + assertEquals("

test
test

", clean); + } + + @Test public void selfClosingOnNonvoidIsError() { + String html = "

test

Two
"; + Parser parser = Parser.htmlParser().setTrackErrors(5); + parser.parseInput(html, ""); + assertEquals(1, parser.getErrors().size()); + assertEquals("18: Tag cannot be self closing; not a void tag", parser.getErrors().get(0).toString()); + assertFalse(Jsoup.isValid(html, Whitelist.relaxed())); + String clean = Jsoup.clean(html, Whitelist.relaxed()); + assertEquals("

test

Two
", StringUtil.normaliseWhitespace(clean)); } } From d381b91393c78dcded43a8ee191d7859cd875f13 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 25 Jun 2017 12:49:49 -0700 Subject: [PATCH 119/774] Note about not calling Parse twice --- CHANGES | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 670809fd46..051d08f985 100644 --- a/CHANGES +++ b/CHANGES @@ -7,7 +7,9 @@ jsoup changelog * When loading content from a URL or a file, the content is now parsed as it streams in from the network or disk, rather than being fully buffered before parsing. This substantially reduces memory consumption & large garbage - objects when loading large files. + objects when loading large files. Note that this change means that a response, once parsed, may not be parsed + again from the same response object unless you call response.body() first which will buffer the full response into + memory first. * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, From c4f6a622ef63687c23a21e795d7a3e32de222d2f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 26 Jun 2017 21:18:21 -0700 Subject: [PATCH 120/774] Test case for #845 (Which doesn't actually work yet, so ignored) --- .../java/org/jsoup/parser/HtmlParserTest.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 6c69edbe55..b399372560 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -4,9 +4,16 @@ import org.jsoup.TextUtil; import org.jsoup.helper.StringUtil; import org.jsoup.integration.ParseTest; -import org.jsoup.nodes.*; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Entities; +import org.jsoup.nodes.FormElement; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import org.jsoup.safety.Whitelist; import org.jsoup.select.Elements; +import org.junit.Ignore; import org.junit.Test; import java.io.File; @@ -511,6 +518,16 @@ public class HtmlParserTest { assertEquals("1\n

23

", doc.body().html()); } + @Ignore // todo: test case for https://github.com/jhy/jsoup/issues/845. Doesn't work yet. + @Test public void handlesMisnestedAInDivs() { + String h = "
child"; + String w = ""; + Document doc = Jsoup.parse(h); + assertEquals( + StringUtil.normaliseWhitespace(w), + StringUtil.normaliseWhitespace(doc.body().html())); + } + @Test public void handlesUnexpectedMarkupInTables() { // whatwg - tests markers in active formatting (if they didn't work, would get in in table) // also tests foster parenting From 195566fa6d4092c6983fbf497c37ae53a188fa90 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 29 Jun 2017 15:24:05 -0700 Subject: [PATCH 121/774] Allow rctext / rcdata tags to self -close Fixes #906 --- CHANGES | 4 ++++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 4 ++-- .../java/org/jsoup/parser/HtmlParserTest.java | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 051d08f985..b4c40b0768 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,10 @@ jsoup changelog * Bugfix: self-closing tags for known empty elements were incorrectly treated as errors. + * Bugfix: fixed an issue where a self-closing title, noframes, or style tag would cause the rest of the page to be + incorrectly parsed as data or text. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index e8373838da..a9a95836aa 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1479,17 +1479,17 @@ private static boolean isWhitespace(String data) { } private static void handleRcData(Token.StartTag startTag, HtmlTreeBuilder tb) { - tb.insert(startTag); tb.tokeniser.transition(TokeniserState.Rcdata); tb.markInsertionMode(); tb.transition(Text); + tb.insert(startTag); } private static void handleRawtext(Token.StartTag startTag, HtmlTreeBuilder tb) { - tb.insert(startTag); tb.tokeniser.transition(TokeniserState.Rawtext); tb.markInsertionMode(); tb.transition(Text); + tb.insert(startTag); } // lists of tags to search through. A little harder to read here, but causes less GC than dynamic varargs. diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index b399372560..36225d272e 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -364,6 +364,24 @@ public class HtmlParserTest { assertEquals("
One
hr text
hr text two", TextUtil.stripNewlines(doc.body().html())); } + @Test public void handlesKnownEmptyNoFrames() { + String h = "<meta name=foo> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One</body></html>"; + Document doc = Jsoup.parse(h); + assertEquals("<html><head><noframes> One", TextUtil.stripNewlines(doc.html())); + } + + @Test public void handlesKnownEmptyStyle() { + String h = " One"; + Document doc = Jsoup.parse(h); + assertEquals(" One", TextUtil.stripNewlines(doc.html())); + } + + @Test public void handlesKnownEmptyTitle() { + String h = "<meta name=foo> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One</body></html>"; + Document doc = Jsoup.parse(h); + assertEquals("<html><head><title> One", TextUtil.stripNewlines(doc.html())); + } + @Test public void handlesSolidusAtAttributeEnd() { // this test makes sure [link] is parsed as [link], not [link] String h = "link"; From cde6403a3c881403a7cf06f4d7d087464038daf6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 30 Jun 2017 20:49:36 -0700 Subject: [PATCH 122/774] Added bufferUp method to response --- CHANGES | 4 ++-- src/main/java/org/jsoup/Connection.java | 11 ++++++++++- src/main/java/org/jsoup/helper/HttpConnection.java | 6 ++++++ .../java/org/jsoup/integration/UrlConnectTest.java | 11 +++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index b4c40b0768..b84704fd74 100644 --- a/CHANGES +++ b/CHANGES @@ -8,8 +8,8 @@ jsoup changelog * When loading content from a URL or a file, the content is now parsed as it streams in from the network or disk, rather than being fully buffered before parsing. This substantially reduces memory consumption & large garbage objects when loading large files. Note that this change means that a response, once parsed, may not be parsed - again from the same response object unless you call response.body() first which will buffer the full response into - memory first. + again from the same response object unless you call response.bufferUp() first, which will buffer the full response + into memory. * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 92ec55c7ed..7651ed205f 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -651,7 +651,8 @@ interface Response extends Base { String contentType(); /** - * Parse the body of the response as a Document. + * Read and parse the body of the response as a Document. If you intend to parse the same response multiple + * times, you should {@link #bufferUp()} first. * @return a parsed Document * @throws IOException on error */ @@ -668,6 +669,14 @@ interface Response extends Base { * @return body bytes */ byte[] bodyAsBytes(); + + /** + * Read the body of the response into a local buffer, so that {@link #parse()} may be called repeatedly on the + * same connection response (otherwise, once the response is read, its InputStream will have been drained and + * may not be re-read). Calling {@link #body() } or {@link #bodyAsBytes()} has the same effect. If the requ + * @return this response, for chaining + */ + Response bufferUp(); } /** diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 21a43f65fc..f00ebef1ca 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -814,6 +814,12 @@ public byte[] bodyAsBytes() { return byteData.array(); } + @Override + public Connection.Response bufferUp() { + prepareByteData(); + return this; + } + // set up connection defaults, and details from request private static HttpURLConnection createConnection(Connection.Request req) throws IOException { final HttpURLConnection conn = (HttpURLConnection) ( diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 993be8c5f5..4a9dbf8f14 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -872,6 +872,17 @@ public void run() { Connection.Response res = Jsoup.connect(echoURL).execute(); Document doc = res.parse(); assertTrue(doc.title().contains("Environment")); + Document doc2 = res.parse(); // should blow up because the response input stream has been drained + } + + @Test public void multipleParsesOkAfterBufferUp() throws IOException { + Connection.Response res = Jsoup.connect(echoURL).execute().bufferUp(); + + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + Document doc2 = res.parse(); + assertTrue(doc2.title().contains("Environment")); } + } From a05130322dc311c2aeb63227d4890294e4a3703d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 30 Jun 2017 21:07:04 -0700 Subject: [PATCH 123/774] Test for self-closing iframe Confirms #382 --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 36225d272e..fa665e2fce 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -382,6 +382,12 @@ public class HtmlParserTest { assertEquals(" One", TextUtil.stripNewlines(doc.html())); } + @Test public void handlesKnownEmptyIframe() { + String h = "

One

Two

", TextUtil.stripNewlines(doc.html())); + } + @Test public void handlesSolidusAtAttributeEnd() { // this test makes sure [link] is parsed as [link], not [link] String h = "link"; From 9eab14f2c4e581102bb5f8fa9943f7c0ca541016 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 30 Jun 2017 22:43:20 -0700 Subject: [PATCH 124/774] Updated impl, test, and changes --- CHANGES | 3 ++ src/main/java/org/jsoup/nodes/Element.java | 27 ++++++---------- .../java/org/jsoup/nodes/ElementTest.java | 32 +++++++++---------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index b84704fd74..67def96412 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog into memory. + * Added Element.appendTo(parent) to simplify slinging elements about. + + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index af4d97cc6c..40bc95c573 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -359,23 +359,16 @@ public Element appendChild(Node child) { } /** - * Add this node as a child node to the given parent - * - * @param parent node in which this node will be appended - * @return this element, so that you can continue modifying the element - */ - public Element appendTo(Node parent) { - Validate.notNull(parent); - if (this.parentNode != null) { - this.parentNode.removeChild(this); - } - - parent.ensureChildNodes(); - parent.childNodes.add(this); - setSiblingIndex(parent.childNodes.size() - 1); - setParentNode(parent); - return this; - } + * Add this element to the supplied parent element, as its next child. + * + * @param parent element to which this element will be appended + * @return this element, so that you can continue modifying the element + */ + public Element appendTo(Element parent) { + Validate.notNull(parent); + parent.appendChild(this); + return this; + } /** * Add a node to the start of this element's children. diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index a35a680f5d..7c504725ec 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -14,14 +14,10 @@ import java.util.Map; import java.util.Set; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1136,17 +1132,21 @@ public void testIs() { @Test public void testAppendTo() { String parentHtml = "
"; - String childHtml = "
"; - - Element parentElement = Jsoup.parse(parentHtml).getElementsByClass("a").first(); - Element childElement = Jsoup.parse(childHtml).getElementsByClass("b").first(); - - childElement.attr("class", "test-class").appendTo(parentElement).attr("id", "testId"); - assertEquals("test-class", childElement.attr("class")); - assertEquals("testId", childElement.attr("id")); - assertThat(parentElement.attr("id"), not(equalTo("testId"))); - assertThat(parentElement.attr("class"), not(equalTo("test-class"))); - assertSame(childElement, parentElement.children().first()); - assertSame(parentElement, childElement.parent()); + String childHtml = "

Two

"; + + Document parentDoc = Jsoup.parse(parentHtml); + Element parent = parentDoc.body(); + Document childDoc = Jsoup.parse(childHtml); + + Element div = childDoc.select("div").first(); + Element p = childDoc.select("p").first(); + Element appendTo1 = div.appendTo(parent); + assertEquals(div, appendTo1); + + Element appendTo2 = p.appendTo(div); + assertEquals(p, appendTo2); + + assertEquals("
\n
\n

Two

\n
", parentDoc.body().html()); + assertEquals("", childDoc.body().html()); // got moved out } } From d3f02400eca1c5cbf84212c46cebcff511536655 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 30 Jun 2017 23:56:59 -0700 Subject: [PATCH 125/774] Treat nbsp chars are spaces when normalizing text Still output   in output HTML, but text provides plain space. --- CHANGES | 2 ++ src/main/java/org/jsoup/helper/StringUtil.java | 15 +++++++++++++-- src/main/java/org/jsoup/parser/TokenQueue.java | 2 +- src/test/java/org/jsoup/nodes/ElementTest.java | 16 ++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 67def96412..ae6a8d47f3 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,8 @@ jsoup changelog * Added Element.appendTo(parent) to simplify slinging elements about. + * Updated Element.text() and the :contains(text) selector to consider   character as spaces. + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index f0dd8303de..f600b90f0a 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -98,14 +98,25 @@ public static boolean isNumeric(String string) { } /** - * Tests if a code point is "whitespace" as defined in the HTML spec. + * Tests if a code point is "whitespace" as defined in the HTML spec. Used for output HTML. * @param c code point to test * @return true if code point is whitespace, false otherwise + * @see #isActuallyWhitespace(int) */ public static boolean isWhitespace(int c){ return c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r'; } + /** + * Tests if a code point is "whitespace" as defined by what it looks like. Used for Element.text etc. + * @param c code point to test + * @return true if code point is whitespace, false otherwise + */ + public static boolean isActuallyWhitespace(int c){ + return c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == 160; + // 160 is   (non-breaking space). Not in the spec but expected. + } + /** * Normalise the whitespace within this string; multiple spaces collapse to a single, and all whitespace characters * (e.g. newline, tab) convert to a simple space @@ -132,7 +143,7 @@ public static void appendNormalisedWhitespace(StringBuilder accum, String string int c; for (int i = 0; i < len; i+= Character.charCount(c)) { c = string.codePointAt(i); - if (isWhitespace(c)) { + if (isActuallyWhitespace(c)) { if ((stripLeading && !reachedNonWhite) || lastWasWhite) continue; accum.append(' '); diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 57a23d79eb..8f3b070128 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -287,7 +287,7 @@ else if (c.equals(close)) } while (depth > 0); final String out = (end >= 0) ? queue.substring(start, end) : ""; if (depth > 0) {// ran out of queue before seeing enough ) - Validate.fail("Did not find balanced maker at " + out); + Validate.fail("Did not find balanced marker at '" + out + "'"); } return out; } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 7c504725ec..e59e338891 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1149,4 +1149,20 @@ public void testAppendTo() { assertEquals("
\n
\n

Two

\n
", parentDoc.body().html()); assertEquals("", childDoc.body().html()); // got moved out } + + @Test public void testNormalizesNbspInText() { + String escaped = "You can't always get what you want."; + String withNbsp = "You can't always get what you want."; // there is an nbsp char in there + Document doc = Jsoup.parse("

" + escaped); + Element p = doc.select("p").first(); + assertEquals("You can't always get what you want.", p.text()); // text is normalized + + assertEquals("

" + escaped + "

", p.outerHtml()); // html / whole text keeps   + assertEquals(withNbsp, p.textNodes().get(0).getWholeText()); + assertEquals(160, withNbsp.charAt(29)); + + Element matched = doc.select("p:contains(get what you want)").first(); + assertEquals("p", matched.nodeName()); + assertTrue(matched.is(":containsOwn(get what you want)")); + } } From c63ccf8324539deddab636dd3c8003201024af1a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 1 Jul 2017 00:36:53 -0700 Subject: [PATCH 126/774] Less StringBuilder GC in text() Need to roll out further. --- .../java/org/jsoup/helper/StringUtil.java | 21 +++++++++++++++++++ src/main/java/org/jsoup/nodes/Element.java | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index f600b90f0a..b96cf67f06 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -207,6 +207,27 @@ public static String resolve(final String baseUrl, final String relUrl) { } catch (MalformedURLException e) { return ""; } + } + /** + * Maintains a cached StringBuilder, to minimize new StringBuilder GCs. Prevents it from growing to big per thread. + * @return an empty StringBuilder + */ + // todo: roll this out everywhere + public static StringBuilder stringBuilder() { + StringBuilder sb = stringLocal.get(); + if (sb.length() > MaxCachedBuilderSize) { + sb = new StringBuilder(MaxCachedBuilderSize); + stringLocal.set(sb); + } + sb.delete(0, sb.length()); + return sb; + + } + private static final ThreadLocal stringLocal = new ThreadLocal<>(); + private static final int MaxCachedBuilderSize = 16 * 1024; + static { + stringLocal.set(new StringBuilder(MaxCachedBuilderSize)); } + } diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 40bc95c573..3a9154e412 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -970,7 +970,7 @@ public Elements getAllElements() { * @see #textNodes() */ public String text() { - final StringBuilder accum = new StringBuilder(); + final StringBuilder accum = StringUtil.stringBuilder(); new NodeTraversor(new NodeVisitor() { public void head(Node node, int depth) { if (node instanceof TextNode) { @@ -1303,7 +1303,7 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) thr * @see #outerHtml() */ public String html() { - StringBuilder accum = new StringBuilder(); + StringBuilder accum = StringUtil.stringBuilder(); html(accum); return getOutputSettings().prettyPrint() ? accum.toString().trim() : accum.toString(); } From 5e386d51f396deec5743ca9cc4e3f0e7559d3956 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 1 Jul 2017 15:38:54 -0700 Subject: [PATCH 127/774] Minor perf improvements through lower garbage objects. --- CHANGES | 2 ++ .../java/org/jsoup/helper/HttpConnection.java | 6 +++--- .../java/org/jsoup/helper/StringUtil.java | 21 ++++++++++++------- src/main/java/org/jsoup/nodes/Document.java | 11 +++++++--- .../java/org/jsoup/parser/TokenQueue.java | 2 +- src/main/java/org/jsoup/parser/Tokeniser.java | 3 ++- src/main/java/org/jsoup/select/Elements.java | 7 ++++++- 7 files changed, 35 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index ae6a8d47f3..a323425a5f 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,8 @@ jsoup changelog into memory. + * Performance improvements in text and HTML generation (through less GC). + * Added Element.appendTo(parent) to simplify slinging elements about. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index f00ebef1ca..c5c032d1f6 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -972,7 +972,7 @@ void processResponseHeaders(Map> resHeaders) { if (values.size() == 1) header(name, values.get(0)); else if (values.size() > 1) { - StringBuilder accum = new StringBuilder(); + StringBuilder accum = StringUtil.stringBuilder(); for (int i = 0; i < values.size(); i++) { final String val = values.get(i); if (i != 0) @@ -1051,7 +1051,7 @@ private static void writePost(final Connection.Request req, final OutputStream o } private static String getRequestCookieString(Connection.Request req) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.stringBuilder(); boolean first = true; for (Map.Entry cookie : req.cookies().entrySet()) { if (!first) @@ -1067,7 +1067,7 @@ private static String getRequestCookieString(Connection.Request req) { // for get url reqs, serialise the data map into the url private static void serialiseRequestUrl(Connection.Request req) throws IOException { URL in = req.url(); - StringBuilder url = new StringBuilder(); + StringBuilder url = StringUtil.stringBuilder(); boolean first = true; // reconstitute the query, ready for appends url diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index b96cf67f06..f8a8bc0f52 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -124,7 +124,7 @@ public static boolean isActuallyWhitespace(int c){ * @return normalised string */ public static String normaliseWhitespace(String string) { - StringBuilder sb = new StringBuilder(string.length()); + StringBuilder sb = StringUtil.stringBuilder(); appendNormalisedWhitespace(sb, string, false); return sb.toString(); } @@ -211,23 +211,28 @@ public static String resolve(final String baseUrl, final String relUrl) { /** * Maintains a cached StringBuilder, to minimize new StringBuilder GCs. Prevents it from growing to big per thread. + * Care must be taken to not grab more than one in the same stack (not locked or mutexed or anything). * @return an empty StringBuilder */ - // todo: roll this out everywhere public static StringBuilder stringBuilder() { StringBuilder sb = stringLocal.get(); if (sb.length() > MaxCachedBuilderSize) { sb = new StringBuilder(MaxCachedBuilderSize); stringLocal.set(sb); + } else { + sb.delete(0, sb.length()); } - sb.delete(0, sb.length()); return sb; } - private static final ThreadLocal stringLocal = new ThreadLocal<>(); - private static final int MaxCachedBuilderSize = 16 * 1024; - static { - stringLocal.set(new StringBuilder(MaxCachedBuilderSize)); - } + + private static final int MaxCachedBuilderSize = 8 * 1024; + private static final ThreadLocal stringLocal = new ThreadLocal(){ + @Override + protected StringBuilder initialValue() { + return new StringBuilder(MaxCachedBuilderSize); + } + }; + } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 638e5067a0..c6e57001d2 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -8,6 +8,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -369,13 +370,16 @@ public static class OutputSettings implements Cloneable { public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; - private Charset charset = Charset.forName("UTF-8"); + private Charset charset; + private final ThreadLocal encoder = new ThreadLocal<>(); // enables the doc to be shared in multiple threads, without creating new encoders on every travers private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; private Syntax syntax = Syntax.html; - public OutputSettings() {} + public OutputSettings() { + charset(StandardCharsets.UTF_8); + } /** * Get the document's current HTML escape mode: base, which provides a limited set of named HTML @@ -419,6 +423,7 @@ public Charset charset() { */ public OutputSettings charset(Charset charset) { this.charset = charset; + encoder.set(charset.newEncoder()); return this; } @@ -433,7 +438,7 @@ public OutputSettings charset(String charset) { } CharsetEncoder encoder() { - return charset.newEncoder(); + return encoder.get(); } /** diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 8f3b070128..96e30e858c 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -298,7 +298,7 @@ else if (c.equals(close)) * @return unescaped string */ public static String unescape(String in) { - StringBuilder out = new StringBuilder(); + StringBuilder out = StringUtil.stringBuilder(); char last = 0; for (char c : in.toCharArray()) { if (c == ESC) { diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index b0479400a7..b031c9b317 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -1,5 +1,6 @@ package org.jsoup.parser; +import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.Entities; @@ -258,7 +259,7 @@ boolean currentNodeInHtmlNS() { * @return unescaped string from reader */ String unescapeEntities(boolean inAttribute) { - StringBuilder builder = new StringBuilder(); + StringBuilder builder = StringUtil.stringBuilder(); while (!reader.isEmpty()) { builder.append(reader.consumeTo('&')); if (reader.matches('&')) { diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 68f9773ced..9b100c79ab 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -5,7 +5,12 @@ import org.jsoup.nodes.FormElement; import org.jsoup.nodes.Node; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; /** A list of {@link Element}s, with methods that act on every element in the list. From f71712ba5d28df09c9a5b6e3c8a37f05f5e3372d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 1 Jul 2017 23:59:26 -0700 Subject: [PATCH 128/774] Project :: Slimfast Refactored node hierarchy so that leaf nodes (text, comments, scripts, etc) use 24 bytes vs 40 bytes each. --- CHANGES | 4 + src/main/java/org/jsoup/nodes/Attributes.java | 26 ++- src/main/java/org/jsoup/nodes/Comment.java | 18 +- src/main/java/org/jsoup/nodes/DataNode.java | 21 ++- src/main/java/org/jsoup/nodes/Document.java | 13 +- .../java/org/jsoup/nodes/DocumentType.java | 38 +++- src/main/java/org/jsoup/nodes/Element.java | 93 ++++++++-- src/main/java/org/jsoup/nodes/LeafNode.java | 95 ++++++++++ src/main/java/org/jsoup/nodes/Node.java | 173 ++++++++---------- src/main/java/org/jsoup/nodes/TextNode.java | 97 ++++------ .../java/org/jsoup/nodes/XmlDeclaration.java | 67 ++++--- .../org/jsoup/parser/HtmlTreeBuilder.java | 6 +- .../jsoup/parser/HtmlTreeBuilderState.java | 3 +- .../java/org/jsoup/parser/XmlTreeBuilder.java | 9 +- src/main/java/org/jsoup/safety/Cleaner.java | 4 +- .../java/org/jsoup/nodes/DocumentTest.java | 2 +- .../org/jsoup/nodes/DocumentTypeTest.java | 14 +- .../java/org/jsoup/nodes/ElementTest.java | 2 +- .../java/org/jsoup/nodes/TextNodeTest.java | 12 +- 19 files changed, 436 insertions(+), 261 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/LeafNode.java diff --git a/CHANGES b/CHANGES index a323425a5f..b97e63412c 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,10 @@ jsoup changelog * Performance improvements in text and HTML generation (through less GC). + * Reduced memory consumption of text, scripts, and comments in the DOM by 40%, by refactoring the node + hierarchy to not track childnodes or attributes by default for lead nodes. For the average document, that's about a + 30% memory reduction. + * Added Element.appendTo(parent) to simplify slinging elements about. diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index b014941c81..ba9a91deb6 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -77,13 +77,15 @@ private Attribute getAttributeIgnoreCase(String key) { } /** - Set a new attribute, or replace an existing one by key. - @param key attribute key - @param value attribute value + * Set a new attribute, or replace an existing one by key. + * @param key attribute key + * @param value attribute value + * @return these attributes, for chaining */ - public void put(String key, String value) { + public Attributes put(String key, String value) { Attribute attr = new Attribute(key, value); put(attr); + return this; } void putIgnoreCase(String key, String value) { @@ -96,26 +98,30 @@ void putIgnoreCase(String key, String value) { } /** - Set a new boolean attribute, remove attribute if value is false. - @param key attribute key - @param value attribute value - */ - public void put(String key, boolean value) { + * Set a new boolean attribute, remove attribute if value is false. + * @param key attribute key + * @param value attribute value + * @return these attributes, for chaining + */ + public Attributes put(String key, boolean value) { if (value) put(new BooleanAttribute(key)); else remove(key); + return this; } /** Set a new attribute, or replace an existing one by key. @param attribute attribute + @return these attributes, for chaining */ - public void put(Attribute attribute) { + public Attributes put(Attribute attribute) { Validate.notNull(attribute); if (attributes == null) attributes = new LinkedHashMap<>(2); attributes.put(attribute.getKey(), attribute); + return this; } /** diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index 40b87c98f8..e467a39084 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -6,17 +6,25 @@ A comment node. @author Jonathan Hedley, jonathan@hedley.net */ -public class Comment extends Node { +public class Comment extends LeafNode { private static final String COMMENT_KEY = "comment"; /** Create a new comment node. @param data The contents of the comment - @param baseUri base URI + */ + public Comment(String data) { + value = data; + } + + /** + Create a new comment node. + @param data The contents of the comment + @param baseUri base URI not used. This is a leaf node. + @deprecated */ public Comment(String data, String baseUri) { - super(baseUri); - attributes.put(COMMENT_KEY, data); + this(data); } public String nodeName() { @@ -28,7 +36,7 @@ public String nodeName() { @return comment content */ public String getData() { - return attributes.get(COMMENT_KEY); + return coreValue(); } void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { diff --git a/src/main/java/org/jsoup/nodes/DataNode.java b/src/main/java/org/jsoup/nodes/DataNode.java index b58ad35a4b..b71cd4aefe 100644 --- a/src/main/java/org/jsoup/nodes/DataNode.java +++ b/src/main/java/org/jsoup/nodes/DataNode.java @@ -6,17 +6,24 @@ A data node, for contents of style, script tags etc, where contents should not show in text(). @author Jonathan Hedley, jonathan@hedley.net */ -public class DataNode extends Node{ - private static final String DATA_KEY = "data"; +public class DataNode extends LeafNode { /** Create a new DataNode. @param data data contents - @param baseUri base URI + */ + public DataNode(String data) { + value = data; + } + + /** + Create a new DataNode. + @param data data contents + @param baseUri Unused, Leaf Nodes do not hold base URis + @deprecated */ public DataNode(String data, String baseUri) { - super(baseUri); - attributes.put(DATA_KEY, data); + this(data); } public String nodeName() { @@ -28,7 +35,7 @@ public String nodeName() { @return data */ public String getWholeData() { - return attributes.get(DATA_KEY); + return coreValue(); } /** @@ -37,7 +44,7 @@ public String getWholeData() { * @return this node, for chaining */ public DataNode setWholeData(String data) { - attributes.put(DATA_KEY, data); + coreValue(data); return this; } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index c6e57001d2..3c54d4bc51 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -150,7 +150,7 @@ private void normaliseTextNodes(Element element) { for (int i = toMove.size()-1; i >= 0; i--) { Node node = toMove.get(i); element.removeChild(node); - body().prependChild(new TextNode(" ", "")); + body().prependChild(new TextNode(" ")); body().prependChild(node); } } @@ -163,7 +163,7 @@ private void normaliseStructure(String tag, Element htmlEl) { List toMove = new ArrayList<>(); for (int i = 1; i < elements.size(); i++) { Node dupe = elements.get(i); - toMove.addAll(dupe.childNodes); + toMove.addAll(dupe.ensureChildNodes()); dupe.remove(); } @@ -181,8 +181,9 @@ private Element findFirstElementByTagName(String tag, Node node) { if (node.nodeName().equals(tag)) return (Element) node; else { - for (Node child: node.childNodes) { - Element found = findFirstElementByTagName(tag, child); + int size = node.childNodeSize(); + for (int i = 0; i < size; i++) { + Element found = findFirstElementByTagName(tag, node.childNode(i)); if (found != null) return found; } @@ -342,14 +343,14 @@ private void ensureMetaCharsetElement() { decl.attr("version", "1.0"); } } else { - decl = new XmlDeclaration("xml", baseUri, false); + decl = new XmlDeclaration("xml", false); decl.attr("version", "1.0"); decl.attr("encoding", charset().displayName()); prependChild(decl); } } else { - XmlDeclaration decl = new XmlDeclaration("xml", baseUri, false); + XmlDeclaration decl = new XmlDeclaration("xml", false); decl.attr("version", "1.0"); decl.attr("encoding", charset().displayName()); diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index 4e7730b128..7d5d42f14a 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -1,14 +1,15 @@ package org.jsoup.nodes; -import java.io.IOException; - import org.jsoup.helper.StringUtil; -import org.jsoup.nodes.Document.OutputSettings.*; +import org.jsoup.nodes.Document.OutputSettings.Syntax; + +import java.io.IOException; /** * A {@code } node. */ -public class DocumentType extends Node { +public class DocumentType extends LeafNode { + // todo needs a bit of a chunky cleanup. this level of detail isn't needed public static final String PUBLIC_KEY = "PUBLIC"; public static final String SYSTEM_KEY = "SYSTEM"; private static final String NAME = "name"; @@ -22,11 +23,25 @@ public class DocumentType extends Node { * @param name the doctype's name * @param publicId the doctype's public ID * @param systemId the doctype's system ID - * @param baseUri the doctype's base URI */ - public DocumentType(String name, String publicId, String systemId, String baseUri) { - super(baseUri); + public DocumentType(String name, String publicId, String systemId) { + attr(NAME, name); + attr(PUBLIC_ID, publicId); + if (has(PUBLIC_ID)) { + attr(PUB_SYS_KEY, PUBLIC_KEY); + } + attr(SYSTEM_ID, systemId); + } + /** + * Create a new doctype element. + * @param name the doctype's name + * @param publicId the doctype's public ID + * @param systemId the doctype's system ID + * @param baseUri unused + * @deprecated + */ + public DocumentType(String name, String publicId, String systemId, String baseUri) { attr(NAME, name); attr(PUBLIC_ID, publicId); if (has(PUBLIC_ID)) { @@ -40,11 +55,10 @@ public DocumentType(String name, String publicId, String systemId, String baseUr * @param name the doctype's name * @param publicId the doctype's public ID * @param systemId the doctype's system ID - * @param baseUri the doctype's base URI + * @param baseUri unused + * @deprecated */ public DocumentType(String name, String pubSysKey, String publicId, String systemId, String baseUri) { - super(baseUri); - attr(NAME, name); if (pubSysKey != null) { attr(PUB_SYS_KEY, pubSysKey); @@ -52,6 +66,10 @@ public DocumentType(String name, String pubSysKey, String publicId, String syste attr(PUBLIC_ID, publicId); attr(SYSTEM_ID, systemId); } + public void setPubSysKey(String value) { + if (value != null) + attr(PUB_SYS_KEY, value); + } @Override public String nodeName() { diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 3a9154e412..266bd14cfb 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1,5 +1,6 @@ package org.jsoup.nodes; +import org.jsoup.helper.ChangeNotifyingArrayList; import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; @@ -37,10 +38,13 @@ * @author Jonathan Hedley, jonathan@hedley.net */ public class Element extends Node { + private static final List EMPTY_NODES = Collections.emptyList(); + private static final Pattern classSplit = Pattern.compile("\\s+"); private Tag tag; private WeakReference> shadowChildrenRef; // points to child elements shadowed from node children - - private static final Pattern classSplit = Pattern.compile("\\s+"); + List childNodes; + private Attributes attributes; + private String baseUri; /** * Create a new, standalone element. @@ -60,9 +64,11 @@ public Element(String tag) { * @see #appendElement(String) */ public Element(Tag tag, String baseUri, Attributes attributes) { - super(baseUri, attributes); - - Validate.notNull(tag); + Validate.notNull(tag); + Validate.notNull(baseUri); + childNodes = EMPTY_NODES; + this.baseUri = baseUri; + this.attributes = attributes; this.tag = tag; } @@ -75,7 +81,41 @@ public Element(Tag tag, String baseUri, Attributes attributes) { * @see Tag#valueOf(String, ParseSettings) */ public Element(Tag tag, String baseUri) { - this(tag, baseUri, new Attributes()); + this(tag, baseUri, null); + } + + protected List ensureChildNodes() { + if (childNodes == EMPTY_NODES) { + childNodes = new NodeList(4); + } + return childNodes; + } + + @Override + protected boolean hasAttributes() { + return attributes != null; + } + + @Override + public Attributes attributes() { + if (!hasAttributes()) + attributes = new Attributes(); + return attributes; + } + + @Override + public String baseUri() { + return baseUri; + } + + @Override + protected void doSetBaseUri(String baseUri) { + this.baseUri = baseUri; + } + + @Override + public int childNodeSize() { + return childNodes.size(); } @Override @@ -130,7 +170,7 @@ public boolean isBlock() { * @return The id attribute, if present, or an empty string if not. */ public String id() { - return attributes.getIgnoreCase("id"); + return attributes().getIgnoreCase("id"); } /** @@ -155,7 +195,7 @@ public Element attr(String attributeKey, String attributeValue) { * @return this element */ public Element attr(String attributeKey, boolean attributeValue) { - attributes.put(attributeKey, attributeValue); + attributes().put(attributeKey, attributeValue); return this; } @@ -173,7 +213,7 @@ public Element attr(String attributeKey, boolean attributeValue) { * @return a map of {@code key=value} custom data attributes. */ public Map dataset() { - return attributes.dataset(); + return attributes().dataset(); } @Override @@ -458,7 +498,7 @@ public Element prependElement(String tagName) { */ public Element appendText(String text) { Validate.notNull(text); - TextNode node = new TextNode(text, baseUri()); + TextNode node = new TextNode(text); appendChild(node); return this; } @@ -471,7 +511,7 @@ public Element appendText(String text) { */ public Element prependText(String text) { Validate.notNull(text); - TextNode node = new TextNode(text, baseUri()); + TextNode node = new TextNode(text); prependChild(node); return this; } @@ -1052,7 +1092,7 @@ public Element text(String text) { Validate.notNull(text); empty(); - TextNode textNode = new TextNode(text, baseUri); + TextNode textNode = new TextNode(text); appendChild(textNode); return this; @@ -1135,7 +1175,7 @@ public Set classNames() { */ public Element classNames(Set classNames) { Validate.notNull(classNames); - attributes.put("class", StringUtil.join(classNames, " ")); + attributes().put("class", StringUtil.join(classNames, " ")); return this; } @@ -1146,7 +1186,7 @@ public Element classNames(Set classNames) { */ // performance sensitive public boolean hasClass(String className) { - final String classAttr = attributes.getIgnoreCase("class"); + final String classAttr = attributes().getIgnoreCase("class"); final int len = classAttr.length(); final int wantLen = className.length(); @@ -1272,7 +1312,8 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr accum .append("<") .append(tagName()); - attributes.html(accum, out); + if (hasAttributes()) + attributes.html(accum, out); // selfclosing includes unknown tags, isEmpty defines tags that are always empty if (childNodes.isEmpty() && tag.isSelfClosing()) { @@ -1344,4 +1385,26 @@ public String toString() { public Element clone() { return (Element) super.clone(); } + + @Override + protected Element doClone(Node parent) { + Element clone = (Element) super.doClone(parent); + clone.attributes = attributes != null ? attributes.clone() : null; + clone.baseUri = baseUri; + clone.childNodes = new NodeList(childNodes.size()); + + clone.childNodes.addAll(childNodes); + + return clone; + } + + private final class NodeList extends ChangeNotifyingArrayList { + NodeList(int initialCapacity) { + super(initialCapacity); + } + + public void onContentsChanged() { + nodelistChanged(); + } + } } diff --git a/src/main/java/org/jsoup/nodes/LeafNode.java b/src/main/java/org/jsoup/nodes/LeafNode.java new file mode 100644 index 0000000000..77bac9b8ef --- /dev/null +++ b/src/main/java/org/jsoup/nodes/LeafNode.java @@ -0,0 +1,95 @@ +package org.jsoup.nodes; + +import org.jsoup.helper.Validate; + +import java.util.List; + +abstract class LeafNode extends Node { + Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set) + + protected final boolean hasAttributes() { + return value instanceof Attributes; + } + + @Override + public final Attributes attributes() { + ensureAttributes(); + return (Attributes) value; + } + + private void ensureAttributes() { + if (!hasAttributes()) { + Object coreValue = value; + Attributes attributes = new Attributes(); + value = attributes; + if (coreValue != null) + attributes.put(nodeName(), (String) coreValue); + } + } + + String coreValue() { + return attr(nodeName()); + } + + void coreValue(String value) { + attr(nodeName(), value); + } + + @Override + public String attr(String key) { + Validate.notNull(key); + if (!hasAttributes()) { + return key.equals(nodeName()) ? (String) value : EmptyString; + } + return super.attr(key); + } + + @Override + public Node attr(String key, String value) { + if (!hasAttributes() && key.equals(nodeName())) { + this.value = value; + } else { + ensureAttributes(); + super.attr(key, value); + } + return this; + } + + @Override + public boolean hasAttr(String key) { + ensureAttributes(); + return super.hasAttr(key); + } + + @Override + public Node removeAttr(String key) { + ensureAttributes(); + return super.removeAttr(key); + } + + @Override + public String absUrl(String key) { + ensureAttributes(); + return super.absUrl(key); + } + + @Override + public String baseUri() { + return hasParent() ? parent().baseUri() : ""; + } + + @Override + protected void doSetBaseUri(String baseUri) { + // noop + } + + @Override + public int childNodeSize() { + return 0; + } + + @Override + protected List ensureChildNodes() { + throw new UnsupportedOperationException("Leaf Nodes do not have child nodes."); + } +} diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index b82710ee08..9866e43faa 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -1,7 +1,6 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.ChangeNotifyingArrayList; import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.Parser; @@ -22,37 +21,14 @@ @author Jonathan Hedley, jonathan@hedley.net */ public abstract class Node implements Cloneable { - private static final List EMPTY_NODES = Collections.emptyList(); + static final String EmptyString = ""; Node parentNode; - List childNodes; - Attributes attributes; - String baseUri; int siblingIndex; - /** - Create a new Node. - @param baseUri base URI - @param attributes attributes (not null, but may be empty) - */ - protected Node(String baseUri, Attributes attributes) { - Validate.notNull(baseUri); - Validate.notNull(attributes); - - childNodes = EMPTY_NODES; - this.baseUri = baseUri.trim(); - this.attributes = attributes; - } - - protected Node(String baseUri) { - this(baseUri, new Attributes()); - } - /** * Default constructor. Doesn't setup base uri, children, or attributes; use with caution. */ protected Node() { - childNodes = EMPTY_NODES; - attributes = null; } /** @@ -61,6 +37,15 @@ Get the node name of this node. Use for debugging purposes and not logic switchi */ public abstract String nodeName(); + /** + * Check if this Node has an actual Attributes object. + */ + protected abstract boolean hasAttributes(); + + public boolean hasParent() { + return parentNode != null; + } + /** * Get an attribute's value by its key. Case insensitive *

@@ -69,7 +54,7 @@ Get the node name of this node. Use for debugging purposes and not logic switchi *

* E.g.: *
String url = a.attr("abs:href");
- * + * * @param attributeKey The attribute key. * @return The attribute, or empty string if not present (to avoid nulls). * @see #attributes() @@ -78,8 +63,10 @@ Get the node name of this node. Use for debugging purposes and not logic switchi */ public String attr(String attributeKey) { Validate.notNull(attributeKey); + if (!hasAttributes()) + return EmptyString; - String val = attributes.getIgnoreCase(attributeKey); + String val = attributes().getIgnoreCase(attributeKey); if (val.length() > 0) return val; else if (lowerCase(attributeKey).startsWith("abs:")) @@ -91,9 +78,7 @@ else if (lowerCase(attributeKey).startsWith("abs:")) * Get all of the element's attributes. * @return attributes (which implements iterable, in same order as presented in original HTML). */ - public Attributes attributes() { - return attributes; - } + public abstract Attributes attributes(); /** * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is @@ -103,7 +88,7 @@ public Attributes attributes() { * @return this (for chaining) */ public Node attr(String attributeKey, String attributeValue) { - attributes.putIgnoreCase(attributeKey, attributeValue); + attributes().putIgnoreCase(attributeKey, attributeValue); return this; } @@ -117,10 +102,10 @@ public boolean hasAttr(String attributeKey) { if (attributeKey.startsWith("abs:")) { String key = attributeKey.substring("abs:".length()); - if (attributes.hasKeyIgnoreCase(key) && !absUrl(key).equals("")) + if (attributes().hasKeyIgnoreCase(key) && !absUrl(key).equals("")) return true; } - return attributes.hasKeyIgnoreCase(attributeKey); + return attributes().hasKeyIgnoreCase(attributeKey); } /** @@ -130,7 +115,7 @@ public boolean hasAttr(String attributeKey) { */ public Node removeAttr(String attributeKey) { Validate.notNull(attributeKey); - attributes.removeIgnoreCase(attributeKey); + attributes().removeIgnoreCase(attributeKey); return this; } @@ -139,7 +124,7 @@ public Node removeAttr(String attributeKey) { * @return this, for chaining */ public Node clearAttributes() { - Iterator it = attributes.iterator(); + Iterator it = attributes().iterator(); while (it.hasNext()) { it.next(); it.remove(); @@ -151,9 +136,13 @@ public Node clearAttributes() { Get the base URI of this node. @return base URI */ - public String baseUri() { - return baseUri; - } + public abstract String baseUri(); + + /** + * Set the baseUri for just this node (not its descendants), if this Node tracks base URIs. + * @param baseUri + */ + protected abstract void doSetBaseUri(String baseUri); /** Update the base URI of this node and all of its descendants. @@ -164,7 +153,7 @@ public void setBaseUri(final String baseUri) { traverse(new NodeVisitor() { public void head(Node node, int depth) { - node.baseUri = baseUri; + node.doSetBaseUri(baseUri); } public void tail(Node node, int depth) { @@ -188,7 +177,7 @@ public void tail(Node node, int depth) { * As an alternate, you can use the {@link #attr} method with the abs: prefix, e.g.: * String absUrl = linkEl.attr("abs:href"); *

- * + * * @param attributeKey The attribute key * @return An absolute URL if one could be made, or an empty string (not null) if the attribute was missing or * could not be made successfully into a URL. @@ -201,17 +190,19 @@ public String absUrl(String attributeKey) { if (!hasAttr(attributeKey)) { return ""; // nothing to make absolute with } else { - return StringUtil.resolve(baseUri, attr(attributeKey)); + return StringUtil.resolve(baseUri(), attr(attributeKey)); } } + protected abstract List ensureChildNodes(); + /** Get a child node by its 0-based index. @param index index of child node @return the child node at this index. Throws a {@code IndexOutOfBoundsException} if the index is out of bounds. */ public Node childNode(int index) { - return childNodes.get(index); + return ensureChildNodes().get(index); } /** @@ -220,7 +211,7 @@ public Node childNode(int index) { @return list of children. If no children, returns an empty list. */ public List childNodes() { - return Collections.unmodifiableList(childNodes); + return Collections.unmodifiableList(ensureChildNodes()); } /** @@ -229,8 +220,9 @@ public List childNodes() { * @return a deep copy of this node's children */ public List childNodesCopy() { - List children = new ArrayList<>(childNodes.size()); - for (Node node : childNodes) { + final List nodes = ensureChildNodes(); + final ArrayList children = new ArrayList<>(nodes.size()); + for (Node node : nodes) { children.add(node.clone()); } return children; @@ -240,12 +232,10 @@ public List childNodesCopy() { * Get the number of child nodes that this node holds. * @return the number of child nodes that this node holds. */ - public final int childNodeSize() { - return childNodes.size(); - } - + public abstract int childNodeSize(); + protected Node[] childNodesAsArray() { - return childNodes.toArray(new Node[childNodeSize()]); + return ensureChildNodes().toArray(new Node[childNodeSize()]); } /** @@ -274,16 +264,16 @@ public Node root() { node = node.parentNode; return node; } - + /** - * Gets the Document associated with this Node. + * Gets the Document associated with this Node. * @return the Document associated with this Node, or null if there is no such Document. */ public Document ownerDocument() { Node root = root(); return (root instanceof Document) ? (Document) root : null; } - + /** * Remove (delete) this node from the DOM tree. If this node has children, they are also removed. */ @@ -346,7 +336,7 @@ private void addSiblingHtml(int index, String html) { Validate.notNull(html); Validate.notNull(parentNode); - Element context = parent() instanceof Element ? (Element) parent() : null; + Element context = parent() instanceof Element ? (Element) parent() : null; List nodes = Parser.parseFragment(html, context, baseUri()); parentNode.addChildren(index, nodes.toArray(new Node[nodes.size()])); } @@ -392,14 +382,14 @@ public Node wrap(String html) { * Calling {@code element.unwrap()} on the {@code span} element will result in the html: *

{@code

One Two Three
}

* and the {@code "Two "} {@link TextNode} being returned. - * + * * @return the first child of this node, after the node has been unwrapped. Null if the node had no children. * @see #remove() * @see #wrap(String) */ public Node unwrap() { Validate.notNull(parentNode); - + final List childNodes = ensureChildNodes(); Node firstChild = childNodes.size() > 0 ? childNodes.get(0) : null; parentNode.addChildren(siblingIndex, this.childNodesAsArray()); this.remove(); @@ -418,7 +408,7 @@ private Element getDeepChild(Element el) { void nodelistChanged() { // Element overrides this to clear its shadow children elements } - + /** * Replace this node in the DOM with the supplied node. * @param in the node that will will replace the existing node. @@ -441,9 +431,9 @@ protected void replaceChild(Node out, Node in) { Validate.notNull(in); if (in.parentNode != null) in.parentNode.removeChild(in); - + final int index = out.siblingIndex; - childNodes.set(index, in); + ensureChildNodes().set(index, in); in.parentNode = this; in.setSiblingIndex(index); out.parentNode = null; @@ -452,50 +442,48 @@ protected void replaceChild(Node out, Node in) { protected void removeChild(Node out) { Validate.isTrue(out.parentNode == this); final int index = out.siblingIndex; - childNodes.remove(index); + ensureChildNodes().remove(index); reindexChildren(index); out.parentNode = null; } protected void addChildren(Node... children) { //most used. short circuit addChildren(int), which hits reindex children and array copy + final List nodes = ensureChildNodes(); + for (Node child: children) { reparentChild(child); - ensureChildNodes(); - childNodes.add(child); - child.setSiblingIndex(childNodes.size()-1); + nodes.add(child); + child.setSiblingIndex(nodes.size()-1); } } protected void addChildren(int index, Node... children) { Validate.noNullElements(children); - ensureChildNodes(); + final List nodes = ensureChildNodes(); + for (int i = children.length - 1; i >= 0; i--) { Node in = children[i]; reparentChild(in); - childNodes.add(index, in); + nodes.add(index, in); reindexChildren(index); } } - - protected void ensureChildNodes() { - if (childNodes == EMPTY_NODES) { - childNodes = new NodeList(4); - } - } - + protected void reparentChild(Node child) { if (child.parentNode != null) child.parentNode.removeChild(child); child.setParentNode(this); } - + private void reindexChildren(int start) { + final List childNodes = ensureChildNodes(); + for (int i = start; i < childNodes.size(); i++) { childNodes.get(i).setSiblingIndex(i); } } - + /** Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not include this node (a node is not a sibling of itself). @@ -505,7 +493,7 @@ public List siblingNodes() { if (parentNode == null) return Collections.emptyList(); - List nodes = parentNode.childNodes; + List nodes = parentNode.ensureChildNodes(); List siblings = new ArrayList<>(nodes.size() - 1); for (Node node: nodes) if (node != this) @@ -520,8 +508,8 @@ public List siblingNodes() { public Node nextSibling() { if (parentNode == null) return null; // root - - final List siblings = parentNode.childNodes; + + final List siblings = parentNode.ensureChildNodes(); final int index = siblingIndex+1; if (siblings.size() > index) return siblings.get(index); @@ -538,7 +526,7 @@ public Node previousSibling() { return null; // root if (siblingIndex > 0) - return parentNode.childNodes.get(siblingIndex-1); + return parentNode.ensureChildNodes().get(siblingIndex-1); else return null; } @@ -552,7 +540,7 @@ public Node previousSibling() { public int siblingIndex() { return siblingIndex; } - + protected void setSiblingIndex(int siblingIndex) { this.siblingIndex = siblingIndex; } @@ -608,7 +596,7 @@ public T html(T appendable) { outerHtml(appendable); return appendable; } - + public String toString() { return outerHtml(); } @@ -656,15 +644,17 @@ public Node clone() { Node thisClone = doClone(null); // splits for orphan // Queue up nodes that need their children cloned (BFS). - LinkedList nodesToProcess = new LinkedList<>(); + final LinkedList nodesToProcess = new LinkedList<>(); nodesToProcess.add(thisClone); while (!nodesToProcess.isEmpty()) { Node currParent = nodesToProcess.remove(); - for (int i = 0; i < currParent.childNodes.size(); i++) { - Node childClone = currParent.childNodes.get(i).doClone(currParent); - currParent.childNodes.set(i, childClone); + final int size = currParent.childNodeSize(); + for (int i = 0; i < size; i++) { + final List childNodes = currParent.ensureChildNodes(); + Node childClone = childNodes.get(i).doClone(currParent); + childNodes.set(i, childClone); nodesToProcess.add(childClone); } } @@ -687,11 +677,6 @@ protected Node doClone(Node parent) { clone.parentNode = parent; // can be null, to create an orphan split clone.siblingIndex = parent == null ? 0 : siblingIndex; - clone.attributes = attributes != null ? attributes.clone() : null; - clone.baseUri = baseUri; - clone.childNodes = new NodeList(childNodes.size()); - - clone.childNodes.addAll(childNodes); return clone; } @@ -723,14 +708,4 @@ public void tail(Node node, int depth) { } } } - - private final class NodeList extends ChangeNotifyingArrayList { - NodeList(int initialCapacity) { - super(initialCapacity); - } - - public void onContentsChanged() { - nodelistChanged(); - } - } } diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 1f95c61787..1e7f5ae60e 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -1,33 +1,36 @@ package org.jsoup.nodes; -import java.io.IOException; - import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; +import java.io.IOException; + /** A text node. @author Jonathan Hedley, jonathan@hedley.net */ -public class TextNode extends Node { - /* - TextNode is a node, and so by default comes with attributes and children. The attributes are seldom used, but use - memory, and the child nodes are never used. So we don't have them, and override accessors to attributes to create - them as needed on the fly. +public class TextNode extends LeafNode { + + /** + Create a new TextNode representing the supplied (unencoded) text). + + @param text raw text + @see #createFromEncoded(String) */ - private static final String TEXT_KEY = "text"; - String text; + public TextNode(String text) { + value = text; + } /** Create a new TextNode representing the supplied (unencoded) text). @param text raw text - @param baseUri base uri + @param baseUri base uri - ignored for this node type @see #createFromEncoded(String, String) + @deprecated use {@link TextNode(String)} */ public TextNode(String text, String baseUri) { - this.baseUri = baseUri; - this.text = text; + this(text); } public String nodeName() { @@ -49,9 +52,7 @@ public String text() { * @return this, for chaining */ public TextNode text(String text) { - this.text = text; - if (attributes != null) - attributes.put(TEXT_KEY, text); + coreValue(text); return this; } @@ -60,7 +61,7 @@ Get the (unencoded) text of this text node, including any newlines and spaces pr @return text */ public String getWholeText() { - return attributes == null ? text : attributes.get(TEXT_KEY); + return coreValue(); } /** @@ -68,7 +69,7 @@ public String getWholeText() { @return true if this document is empty or only whitespace, false if it contains any text content. */ public boolean isBlank() { - return StringUtil.isBlank(getWholeText()); + return StringUtil.isBlank(coreValue()); } /** @@ -78,11 +79,12 @@ public boolean isBlank() { * @return the newly created text node containing the text after the offset. */ public TextNode splitText(int offset) { + final String text = coreValue(); Validate.isTrue(offset >= 0, "Split offset must be not be negative"); Validate.isTrue(offset < text.length(), "Split offset must not be greater than current text length"); - String head = getWholeText().substring(0, offset); - String tail = getWholeText().substring(offset); + String head = text.substring(0, offset); + String tail = text.substring(offset); text(head); TextNode tailNode = new TextNode(tail, this.baseUri()); if (parent() != null) @@ -97,7 +99,7 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr boolean normaliseWhite = out.prettyPrint() && parent() instanceof Element && !Element.preserveWhitespace(parent()); - Entities.escape(accum, getWholeText(), out, false, normaliseWhite, false); + Entities.escape(accum, coreValue(), out, false, normaliseWhite, false); } void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} @@ -112,10 +114,21 @@ public String toString() { * @param encodedText Text containing encoded HTML (e.g. &lt;) * @param baseUri Base uri * @return TextNode containing unencoded data (e.g. <) + * @deprecated use {@link TextNode#createFromEncoded(String)} instead, as LeafNodes don't carry base URIs. */ public static TextNode createFromEncoded(String encodedText, String baseUri) { String text = Entities.unescape(encodedText); - return new TextNode(text, baseUri); + return new TextNode(text); + } + + /** + * Create a new TextNode from HTML encoded (aka escaped) data. + * @param encodedText Text containing encoded HTML (e.g. &lt;) + * @return TextNode containing unencoded data (e.g. <) + */ + public static TextNode createFromEncoded(String encodedText) { + String text = Entities.unescape(encodedText); + return new TextNode(text); } static String normaliseWhitespace(String text) { @@ -131,47 +144,5 @@ static boolean lastCharIsWhitespace(StringBuilder sb) { return sb.length() != 0 && sb.charAt(sb.length() - 1) == ' '; } - // attribute fiddling. create on first access. - private void ensureAttributes() { - if (attributes == null) { - attributes = new Attributes(); - attributes.put(TEXT_KEY, text); - } - } - - @Override - public String attr(String attributeKey) { - ensureAttributes(); - return super.attr(attributeKey); - } - - @Override - public Attributes attributes() { - ensureAttributes(); - return super.attributes(); - } - - @Override - public Node attr(String attributeKey, String attributeValue) { - ensureAttributes(); - return super.attr(attributeKey, attributeValue); - } - @Override - public boolean hasAttr(String attributeKey) { - ensureAttributes(); - return super.hasAttr(attributeKey); - } - - @Override - public Node removeAttr(String attributeKey) { - ensureAttributes(); - return super.removeAttr(attributeKey); - } - - @Override - public String absUrl(String attributeKey) { - ensureAttributes(); - return super.absUrl(attributeKey); - } } diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index 619cd14a94..602805bd7b 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -1,63 +1,88 @@ package org.jsoup.nodes; +import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import java.io.IOException; /** - An XML Declaration. - - @author Jonathan Hedley, jonathan@hedley.net */ -public class XmlDeclaration extends Node { - private final String name; + * An XML Declaration. + */ +public class XmlDeclaration extends LeafNode { + // todo this impl isn't really right, the data shouldn't be attributes, just a run of text after the name private final boolean isProcessingInstruction; // "); } - void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} + void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { + } @Override public String toString() { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 3fe076ad1c..6ada343afc 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -244,7 +244,7 @@ FormElement insertForm(Token.StartTag startTag, boolean onStack) { } void insert(Token.Comment commentToken) { - Comment comment = new Comment(commentToken.getData(), baseUri); + Comment comment = new Comment(commentToken.getData()); insertNode(comment); } @@ -253,9 +253,9 @@ void insert(Token.Character characterToken) { // characters in script and style go in as datanodes, not text nodes String tagName = currentElement().tagName(); if (tagName.equals("script") || tagName.equals("style")) - node = new DataNode(characterToken.getData(), baseUri); + node = new DataNode(characterToken.getData()); else - node = new TextNode(characterToken.getData(), baseUri); + node = new TextNode(characterToken.getData()); currentElement().appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack. } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index a9a95836aa..bb2a61e9da 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -20,7 +20,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { // todo: quirk state check on doctype ids Token.Doctype d = t.asDoctype(); DocumentType doctype = new DocumentType( - tb.settings.normalizeTag(d.getName()), d.getPubSysKey(), d.getPublicIdentifier(), d.getSystemIdentifier(), tb.getBaseUri()); + tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); + doctype.setPubSysKey(d.getPubSysKey()); tb.getDocument().appendChild(doctype); if (d.isForceQuirks()) tb.getDocument().quirksMode(Document.QuirksMode.quirks); diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 0507243ddd..631a74a152 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -81,7 +81,7 @@ Element insert(Token.StartTag startTag) { } void insert(Token.Comment commentToken) { - Comment comment = new Comment(commentToken.getData(), baseUri); + Comment comment = new Comment(commentToken.getData()); Node insert = comment; if (commentToken.bogus) { // xml declarations are emitted as bogus comments (which is right for html, but not xml) // so we do a bit of a hack and parse the data as an element to pull the attributes out @@ -89,7 +89,7 @@ void insert(Token.Comment commentToken) { if (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))) { Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri, Parser.xmlParser()); Element el = doc.child(0); - insert = new XmlDeclaration(settings.normalizeTag(el.tagName()), comment.baseUri(), data.startsWith("!")); + insert = new XmlDeclaration(settings.normalizeTag(el.tagName()), data.startsWith("!")); insert.attributes().addAll(el.attributes()); } } @@ -97,12 +97,13 @@ void insert(Token.Comment commentToken) { } void insert(Token.Character characterToken) { - Node node = new TextNode(characterToken.getData(), baseUri); + Node node = new TextNode(characterToken.getData()); insertNode(node); } void insert(Token.Doctype d) { - DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPubSysKey(), d.getPublicIdentifier(), d.getSystemIdentifier(), baseUri); + DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); + doctypeNode.setPubSysKey(d.getPubSysKey()); insertNode(doctypeNode); } diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 1223bbe800..34f0b14184 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -119,11 +119,11 @@ public void head(Node source, int depth) { } } else if (source instanceof TextNode) { TextNode sourceText = (TextNode) source; - TextNode destText = new TextNode(sourceText.getWholeText(), source.baseUri()); + TextNode destText = new TextNode(sourceText.getWholeText()); destination.appendChild(destText); } else if (source instanceof DataNode && whitelist.isSafeTag(source.parent().nodeName())) { DataNode sourceData = (DataNode) source; - DataNode destData = new DataNode(sourceData.getWholeData(), source.baseUri()); + DataNode destData = new DataNode(sourceData.getWholeData()); destination.appendChild(destData); } else { // else, we don't care about comments, xml proc instructions, etc numDiscarded++; diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 95b9700f5b..61feee4698 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -403,7 +403,7 @@ private Document createXmlDocument(String version, String charset, boolean addDe doc.outputSettings().syntax(Syntax.xml); if( addDecl == true ) { - XmlDeclaration decl = new XmlDeclaration("xml", "", false); + XmlDeclaration decl = new XmlDeclaration("xml", false); decl.attr("version", version); decl.attr("encoding", charset); doc.prependChild(decl); diff --git a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java index 38110ff787..7f32adb117 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java @@ -14,30 +14,30 @@ public class DocumentTypeTest { @Test public void constructorValidationOkWithBlankName() { - DocumentType fail = new DocumentType("","", "", ""); + DocumentType fail = new DocumentType("","", ""); } @Test(expected = IllegalArgumentException.class) public void constructorValidationThrowsExceptionOnNulls() { - DocumentType fail = new DocumentType("html", null, null, ""); + DocumentType fail = new DocumentType("html", null, null); } @Test public void constructorValidationOkWithBlankPublicAndSystemIds() { - DocumentType fail = new DocumentType("html","", "",""); + DocumentType fail = new DocumentType("html","", ""); } @Test public void outerHtmlGeneration() { - DocumentType html5 = new DocumentType("html", "", "", ""); + DocumentType html5 = new DocumentType("html", "", ""); assertEquals("", html5.outerHtml()); - DocumentType publicDocType = new DocumentType("html", "-//IETF//DTD HTML//", "", ""); + DocumentType publicDocType = new DocumentType("html", "-//IETF//DTD HTML//", ""); assertEquals("", publicDocType.outerHtml()); - DocumentType systemDocType = new DocumentType("html", "", "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd", ""); + DocumentType systemDocType = new DocumentType("html", "", "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"); assertEquals("", systemDocType.outerHtml()); - DocumentType combo = new DocumentType("notHtml", "--public", "--system", ""); + DocumentType combo = new DocumentType("notHtml", "--public", "--system"); assertEquals("", combo.outerHtml()); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index e59e338891..e69ebe47e6 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -783,7 +783,7 @@ public void insertChildrenAtPosition() { List els = new ArrayList(); Element el1 = new Element(Tag.valueOf("span"), "").text("Span1"); Element el2 = new Element(Tag.valueOf("span"), "").text("Span2"); - TextNode tn1 = new TextNode("Text4", ""); + TextNode tn1 = new TextNode("Text4"); els.add(el1); els.add(el2); els.add(tn1); diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index 148a0abb24..42b35ad559 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -12,11 +12,11 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextNodeTest { @Test public void testBlank() { - TextNode one = new TextNode("", ""); - TextNode two = new TextNode(" ", ""); - TextNode three = new TextNode(" \n\n ", ""); - TextNode four = new TextNode("Hello", ""); - TextNode five = new TextNode(" \nHello ", ""); + TextNode one = new TextNode(""); + TextNode two = new TextNode(" "); + TextNode three = new TextNode(" \n\n "); + TextNode four = new TextNode("Hello"); + TextNode five = new TextNode(" \nHello "); assertTrue(one.isBlank()); assertTrue(two.isBlank()); @@ -40,7 +40,7 @@ public class TextNodeTest { tn.text(" POW!"); assertEquals("One two & POW!", TextUtil.stripNewlines(p.html())); - tn.attr("text", "kablam &"); + tn.attr(tn.nodeName(), "kablam &"); assertEquals("kablam &", tn.text()); assertEquals("One two &kablam &", TextUtil.stripNewlines(p.html())); } From aff239790b5344ac99196a931d0d2efc8916156b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Jul 2017 14:29:58 -0700 Subject: [PATCH 129/774] Make sure encoder works across threads --- src/main/java/org/jsoup/nodes/Document.java | 10 ++++++++-- src/test/java/org/jsoup/nodes/DocumentTest.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 3c54d4bc51..05cda42244 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -372,7 +372,13 @@ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset; - private final ThreadLocal encoder = new ThreadLocal<>(); // enables the doc to be shared in multiple threads, without creating new encoders on every travers + // enables the doc to be shared in multiple threads, without creating new encoders on every traverse + private final ThreadLocal encoder = new ThreadLocal() { + @Override + protected CharsetEncoder initialValue() { + return charset.newEncoder(); + } + }; private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; @@ -424,7 +430,7 @@ public Charset charset() { */ public OutputSettings charset(Charset charset) { this.charset = charset; - encoder.set(charset.newEncoder()); + encoder.remove(); return this; } diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 61feee4698..fa767995b5 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -434,4 +434,19 @@ public void testShiftJisRoundtrip() throws Exception { assertTrue("Should have contained a ' ' or a ' '.", output.contains(" ") || output.contains(" ")); } + + @Test public void parseAndHtmlOnDifferentThreads() throws InterruptedException { + String html = "

Alright then.

"; + final Document doc = Jsoup.parse(html); + final String[] out = new String[1]; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + out[0] = doc.select("p").outerHtml(); + } + }); + thread.start(); + thread.join(); + assertEquals("

Alright then.

", out[0]); + } } From 7c8df4ca183550cd1d818b718cd1336a97447775 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Jul 2017 14:29:58 -0700 Subject: [PATCH 130/774] Make sure encoder works across threads --- src/main/java/org/jsoup/nodes/Document.java | 19 +++++++++++--- .../java/org/jsoup/nodes/DocumentTest.java | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 3c54d4bc51..a2448ba040 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -372,7 +372,13 @@ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset; - private final ThreadLocal encoder = new ThreadLocal<>(); // enables the doc to be shared in multiple threads, without creating new encoders on every travers + // enables the doc to be shared in multiple threads, without creating new encoders on every traverse + private final ThreadLocal encoder = new ThreadLocal() { + @Override + protected CharsetEncoder initialValue() { + return charset.newEncoder(); + } + }; private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; @@ -424,7 +430,7 @@ public Charset charset() { */ public OutputSettings charset(Charset charset) { this.charset = charset; - encoder.set(charset.newEncoder()); + encoder.remove(); return this; } @@ -439,7 +445,14 @@ public OutputSettings charset(String charset) { } CharsetEncoder encoder() { - return encoder.get(); + CharsetEncoder ce = encoder.get(); + // check that the charset wasn't changed since accessed in this thread + // (this is probably overkill for something we're not advertising as threadsafe) + if (!ce.charset().equals(charset)) { + encoder.remove(); + ce = encoder.get(); // retrips initialValue() + } + return ce; } /** diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 61feee4698..32c60b802d 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -5,6 +5,7 @@ import org.jsoup.integration.ParseTest; import org.jsoup.nodes.Document.OutputSettings; import org.jsoup.nodes.Document.OutputSettings.Syntax; +import org.jsoup.select.Elements; import org.junit.Ignore; import org.junit.Test; @@ -14,6 +15,7 @@ import java.io.InputStream; import java.io.StringWriter; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -434,4 +436,27 @@ public void testShiftJisRoundtrip() throws Exception { assertTrue("Should have contained a ' ' or a ' '.", output.contains(" ") || output.contains(" ")); } + + @Test public void parseAndHtmlOnDifferentThreads() throws InterruptedException { + String html = "

Alrighty then it's not \uD83D\uDCA9. Next

"; // 💩 + String asci = "

Alrighty then it's not 💩. Next

"; + + final Document doc = Jsoup.parse(html); + final String[] out = new String[1]; + final Elements p = doc.select("p"); + assertEquals(html, p.outerHtml()); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + out[0] = p.outerHtml(); + doc.outputSettings().charset(StandardCharsets.US_ASCII); + } + }); + thread.start(); + thread.join(); + assertEquals(html, out[0]); + assertEquals(StandardCharsets.US_ASCII, doc.outputSettings().charset()); + assertEquals(asci, p.outerHtml()); + } } From ea1fb65e9ff8eee82c4e379dc3236d09a5ab02e1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 4 Jul 2017 13:10:31 -0700 Subject: [PATCH 131/774] Refactored Attributes to be an array pair vs LinkedHashSet Also a couple perf (cpu / garbage) tweaks --- CHANGES | 5 + .../java/org/jsoup/helper/StringUtil.java | 7 +- src/main/java/org/jsoup/nodes/Attribute.java | 101 ++++-- src/main/java/org/jsoup/nodes/Attributes.java | 293 +++++++++++------- .../org/jsoup/nodes/BooleanAttribute.java | 3 +- src/main/java/org/jsoup/nodes/Document.java | 24 +- .../java/org/jsoup/nodes/DocumentType.java | 4 + src/main/java/org/jsoup/nodes/Element.java | 15 +- src/main/java/org/jsoup/nodes/Entities.java | 8 +- src/main/java/org/jsoup/nodes/Node.java | 11 +- .../java/org/jsoup/parser/ParseSettings.java | 5 +- src/main/java/org/jsoup/parser/Token.java | 13 +- .../java/org/jsoup/helper/StringUtilTest.java | 9 + .../java/org/jsoup/nodes/AttributesTest.java | 52 +++- 14 files changed, 355 insertions(+), 195 deletions(-) diff --git a/CHANGES b/CHANGES index b97e63412c..aad87d711c 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,11 @@ jsoup changelog * Reduced memory consumption of text, scripts, and comments in the DOM by 40%, by refactoring the node hierarchy to not track childnodes or attributes by default for lead nodes. For the average document, that's about a 30% memory reduction. + + + * Reduced memory consumption of Elements by refactoring their Attributes to be a simple pair of arrays, vs a + LinkedHashSet. + * Added Element.appendTo(parent) to simplify slinging elements about. diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index f8a8bc0f52..90cc69d935 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -10,8 +10,10 @@ * A minimal String utility class. Designed for internal jsoup use only. */ public final class StringUtil { - // memoised padding up to 10 - private static final String[] padding = {"", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}; + // memoised padding up to 21 + static final String[] padding = {"", " ", " ", " ", " ", " ", " ", " ", " ", + " ", " ", " ", " ", " ", " ", " ", + " ", " ", " ", " ", " "}; /** * Join a collection of strings by a separator @@ -56,7 +58,6 @@ public static String padding(int width) { if (width < padding.length) return padding[width]; - char[] out = new char[width]; for (int i = 0; i < width; i++) out[i] = ' '; diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 38be8235cb..abe5d55b02 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -8,9 +8,8 @@ import java.util.Map; /** - A single key + value attribute. Keys are trimmed and normalised to lower-case. - - @author Jonathan Hedley, jonathan@hedley.net */ + A single key + value attribute. (Only used for presentation.) + */ public class Attribute implements Map.Entry, Cloneable { private static final String[] booleanAttributes = { "allowfullscreen", "async", "autofocus", "checked", "compact", "declare", "default", "defer", "disabled", @@ -20,7 +19,8 @@ public class Attribute implements Map.Entry, Cloneable { }; private String key; - private String value; + private String val; + Attributes parent; // used to update the holding Attributes when the key / value is changed via this interface /** * Create a new attribute from unencoded (raw) key and value. @@ -29,11 +29,21 @@ public class Attribute implements Map.Entry, Cloneable { * @see #createFromEncoded */ public Attribute(String key, String value) { + this(key, value, null); + } + + /** + * Create a new attribute from unencoded (raw) key and value. + * @param key attribute key; case is preserved. + * @param val attribute value + * @param parent the containing Attributes (this Attribute is not automatically added to said Attributes) + * @see #createFromEncoded*/ + public Attribute(String key, String val, Attributes parent) { Validate.notNull(key); - Validate.notNull(value); this.key = key.trim(); Validate.notEmpty(key); // trimming could potentially make empty, so validate here - this.value = value; + this.val = val; + this.parent = parent; } /** @@ -50,7 +60,13 @@ public String getKey() { */ public void setKey(String key) { Validate.notEmpty(key); - this.key = key.trim(); + key = key.trim(); + if (parent != null) { + int i = parent.indexOfKey(this.key); + if (i != Attributes.NotFound) + parent.keys[i] = key; + } + this.key = key; } /** @@ -58,18 +74,22 @@ public void setKey(String key) { @return the attribute value */ public String getValue() { - return value; + return val; } /** Set the attribute value. - @param value the new attribute value; must not be null + @param val the new attribute value; must not be null */ - public String setValue(String value) { - Validate.notNull(value); - String old = this.value; - this.value = value; - return old; + public String setValue(String val) { + String oldVal = parent.get(this.key); + if (parent != null) { + int i = parent.indexOfKey(this.key); + if (i != Attributes.NotFound) + parent.vals[i] = val; + } + this.val = val; + return oldVal; } /** @@ -86,15 +106,19 @@ public String html() { } return accum.toString(); } - - protected void html(Appendable accum, Document.OutputSettings out) throws IOException { + + protected static void html(String key, String val, Appendable accum, Document.OutputSettings out) throws IOException { accum.append(key); - if (!shouldCollapseAttribute(out)) { + if (!shouldCollapseAttribute(key, val, out)) { accum.append("=\""); - Entities.escape(accum, value, out, true, false, false); + Entities.escape(accum, Attributes.checkNotNull(val) , out, true, false, false); accum.append('"'); } } + + protected void html(Appendable accum, Document.OutputSettings out) throws IOException { + html(key, val, accum, out); + } /** Get the string representation of this attribute, implemented as {@link #html()}. @@ -113,10 +137,14 @@ public String toString() { */ public static Attribute createFromEncoded(String unencodedKey, String encodedValue) { String value = Entities.unescape(encodedValue, true); - return new Attribute(unencodedKey, value); + return new Attribute(unencodedKey, value, null); // parent will get set when Put } protected boolean isDataAttribute() { + return isDataAttribute(key); + } + + protected static boolean isDataAttribute(String key) { return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length(); } @@ -127,37 +155,50 @@ protected boolean isDataAttribute() { * @return Returns whether collapsible or not */ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { - return ("".equals(value) || value.equalsIgnoreCase(key)) - && out.syntax() == Document.OutputSettings.Syntax.html - && isBooleanAttribute(); + return shouldCollapseAttribute(key, val, out); } + protected static boolean shouldCollapseAttribute(String key, String val, Document.OutputSettings out) { + // todo: optimize + return (val == null || "".equals(val) || val.equalsIgnoreCase(key)) + && out.syntax() == Document.OutputSettings.Syntax.html + && isBooleanAttribute(key); + } + + /** + * @deprecated + */ protected boolean isBooleanAttribute() { + return Arrays.binarySearch(booleanAttributes, key) >= 0 || val == null; + } + + /** + * Checks if this attribute name is defined as a boolean attribute in HTML5 + */ + protected static boolean isBooleanAttribute(final String key) { return Arrays.binarySearch(booleanAttributes, key) >= 0; } @Override - public boolean equals(Object o) { + public boolean equals(Object o) { // note parent not considered if (this == o) return true; - if (!(o instanceof Attribute)) return false; - + if (o == null || getClass() != o.getClass()) return false; Attribute attribute = (Attribute) o; - if (key != null ? !key.equals(attribute.key) : attribute.key != null) return false; - return !(value != null ? !value.equals(attribute.value) : attribute.value != null); + return val != null ? val.equals(attribute.val) : attribute.val == null; } @Override - public int hashCode() { + public int hashCode() { // note parent not considered int result = key != null ? key.hashCode() : 0; - result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (val != null ? val.hashCode() : 0); return result; } @Override public Attribute clone() { try { - return (Attribute) super.clone(); // only fields are immutable strings key and value, so no more deep copy required + return (Attribute) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index ba9a91deb6..c8c1d0751e 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -7,20 +7,22 @@ import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import static org.jsoup.internal.Normalizer.lowerCase; + /** * The attributes of an Element. *

* Attributes are treated as a map: there can be only one value associated with an attribute key/name. *

*

- * Attribute name and value comparisons are case sensitive. By default for HTML, attribute names are + * Attribute name and value comparisons are generally case sensitive. By default for HTML, attribute names are * normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by * name. *

@@ -29,83 +31,120 @@ */ public class Attributes implements Iterable, Cloneable { protected static final String dataPrefix = "data-"; + private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting + + // manages the key/val arrays + private static final int GrowthFactor = 2; + private static final String[] Empty = {}; + static final int NotFound = -1; + private static final String EmptyString = ""; + + private int size = 0; // number of slots used (not capacity, which is keys.length + String[] keys = Empty; + String[] vals = Empty; + + // check there's room for more + private void checkCapacity(int minNewSize) { + Validate.isTrue(minNewSize >= size); + int curSize = keys.length; + if (curSize >= minNewSize) + return; - private LinkedHashMap attributes = null; - // linked hash map to preserve insertion order. - // null be default as so many elements have no attributes -- saves a good chunk of memory + int newSize = curSize >= InitialCapacity ? size * GrowthFactor : InitialCapacity; + if (minNewSize > newSize) + newSize = minNewSize; + + keys = Arrays.copyOf(keys, newSize); + vals = Arrays.copyOf(vals, newSize); + } + + int indexOfKey(String key) { + Validate.notNull(key); + for (int i = 0; i < size; i++) { + if (key.equals(keys[i])) + return i; + } + return NotFound; + } + + private int indexOfKeyIgnoreCase(String key) { + Validate.notNull(key); + for (int i = 0; i < size; i++) { + if (key.equalsIgnoreCase(keys[i])) + return i; + } + return NotFound; + } + + // we track boolean attributes as null in values - they're just keys. so returns empty for consumers + static final String checkNotNull(String val) { + return val == null ? EmptyString : val; + } /** Get an attribute value by key. @param key the (case-sensitive) attribute key - @return the attribute value if set; or empty string if not set. + @return the attribute value if set; or empty string if not set (or a boolean attribute). @see #hasKey(String) */ public String get(String key) { - Validate.notEmpty(key); - - if (attributes == null) - return ""; - - Attribute attr = attributes.get(key); - return attr != null ? attr.getValue() : ""; + int i = indexOfKey(key); + return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name - * @return the first matching attribute value if set; or empty string if not set. + * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { - Attribute attr = getAttributeIgnoreCase(key); - return attr != null ? attr.getValue() : ""; + int i = indexOfKeyIgnoreCase(key); + return i == NotFound ? EmptyString : checkNotNull(vals[i]); } - private Attribute getAttributeIgnoreCase(String key) { - Validate.notEmpty(key); - if (attributes == null) - return null; - - Attribute attr = attributes.get(key); - if (attr != null) - return attr; - - for (String attrKey : attributes.keySet()) { - if (attrKey.equalsIgnoreCase(key)) - return attributes.get(attrKey); - } - return null; + // adds without checking if this key exists + private void add(String key, String value) { + checkCapacity(size + 1); + keys[size] = key; + vals[size] = value; + size++; } /** * Set a new attribute, or replace an existing one by key. - * @param key attribute key + * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { - Attribute attr = new Attribute(key, value); - put(attr); + int i = indexOfKey(key); + if (i != NotFound) + vals[i] = value; + else + add(key, value); return this; } void putIgnoreCase(String key, String value) { - Attribute oldAttr = getAttributeIgnoreCase(key); - if (oldAttr != null && !oldAttr.getKey().equals(key)) { - attributes.remove(oldAttr.getKey()); + int i = indexOfKeyIgnoreCase(key); + if (i != NotFound) { + vals[i] = value; + if (!keys[i].equals(key)) // case changed, update + keys[i] = key; } - - put(key, value); + else + add(key, value); } /** * Set a new boolean attribute, remove attribute if value is false. - * @param key attribute key + * @param key case insensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, boolean value) { if (value) - put(new BooleanAttribute(key)); + putIgnoreCase(key, null); else remove(key); return this; @@ -113,26 +152,37 @@ public Attributes put(String key, boolean value) { /** Set a new attribute, or replace an existing one by key. - @param attribute attribute + @param attribute attribute with case sensitive key @return these attributes, for chaining */ public Attributes put(Attribute attribute) { Validate.notNull(attribute); - if (attributes == null) - attributes = new LinkedHashMap<>(2); - attributes.put(attribute.getKey(), attribute); + put(attribute.getKey(), attribute.getValue()); + attribute.parent = this; return this; } + // removes and shifts up + private void remove(int index) { + Validate.isFalse(index >= size); + int shifted = size - index - 1; + if (shifted > 0) { + System.arraycopy(keys, index + 1, keys, index, shifted); + System.arraycopy(vals, index + 1, vals, index, shifted); + } + size--; + keys[size] = null; // release hold + vals[size] = null; + } + /** Remove an attribute by key. Case sensitive. @param key attribute key to remove */ public void remove(String key) { - Validate.notEmpty(key); - if (attributes == null) - return; - attributes.remove(key); + int i = indexOfKey(key); + if (i != NotFound) + remove(i); } /** @@ -140,14 +190,9 @@ public void remove(String key) { @param key attribute key to remove */ public void removeIgnoreCase(String key) { - Validate.notEmpty(key); - if (attributes == null) - return; - for (Iterator it = attributes.keySet().iterator(); it.hasNext(); ) { - String attrKey = it.next(); - if (attrKey.equalsIgnoreCase(key)) - it.remove(); - } + int i = indexOfKeyIgnoreCase(key); + if (i != NotFound) + remove(i); } /** @@ -156,7 +201,7 @@ public void removeIgnoreCase(String key) { @return true if key exists, false otherwise */ public boolean hasKey(String key) { - return attributes != null && attributes.containsKey(key); + return indexOfKey(key) != NotFound; } /** @@ -165,13 +210,7 @@ public boolean hasKey(String key) { @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { - if (attributes == null) - return false; - for (String attrKey : attributes.keySet()) { - if (attrKey.equalsIgnoreCase(key)) - return true; - } - return false; + return indexOfKeyIgnoreCase(key) != NotFound; } /** @@ -179,9 +218,7 @@ public boolean hasKeyIgnoreCase(String key) { @return size */ public int size() { - if (attributes == null) - return 0; - return attributes.size(); + return size; } /** @@ -191,31 +228,49 @@ public int size() { public void addAll(Attributes incoming) { if (incoming.size() == 0) return; - if (attributes == null) - attributes = new LinkedHashMap<>(incoming.size()); - attributes.putAll(incoming.attributes); + checkCapacity(size + incoming.size); + + for (Attribute attr : incoming) { + // todo - should this be case insensitive? + put(attr); + } + } public Iterator iterator() { - if (attributes == null || attributes.isEmpty()) { - return Collections.emptyList().iterator(); - } + return new Iterator() { + int i = 0; - return attributes.values().iterator(); + @Override + public boolean hasNext() { + return i < size; + } + + @Override + public Attribute next() { + final Attribute attr = new Attribute(keys[i], vals[i], Attributes.this); + i++; + return attr; + } + + @Override + public void remove() { + Attributes.this.remove(--i); // next() advanced, so rewind + } + }; } /** - Get the attributes as a List, for iteration. Do not modify the keys of the attributes via this view, as changes - to keys will not be recognised in the containing set. - @return an view of the attributes as a List. + Get the attributes as a List, for iteration. + @return an view of the attributes as an unmodifialbe List. */ public List asList() { - if (attributes == null) - return Collections.emptyList(); - - List list = new ArrayList<>(attributes.size()); - for (Map.Entry entry : attributes.entrySet()) { - list.add(entry.getValue()); + ArrayList list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + Attribute attr = vals[i] == null ? + new BooleanAttribute(keys[i]) : // deprecated class, but maybe someone still wants it + new Attribute(keys[i], vals[i], Attributes.this); + list.add(attr); } return Collections.unmodifiableList(list); } @@ -226,7 +281,7 @@ public List asList() { * @return map of custom data attributes. */ public Map dataset() { - return new Dataset(); + return new Dataset(this); } /** @@ -244,14 +299,22 @@ public String html() { return accum.toString(); } - void html(Appendable accum, Document.OutputSettings out) throws IOException { - if (attributes == null) - return; - - for (Map.Entry entry : attributes.entrySet()) { - Attribute attribute = entry.getValue(); - accum.append(" "); - attribute.html(accum, out); + final void html(final Appendable accum, final Document.OutputSettings out) throws IOException { + final int sz = size; + for (int i = 0; i < sz; i++) { + // inlined from Attribute.html() + final String key = keys[i]; + final String val = vals[i]; + accum.append(' ').append(key); + + // collapse checked=null, checked="", checked=checked; write out others + if (!(out.syntax() == Document.OutputSettings.Syntax.html + && (val == null || val.equals(key) && Attribute.isBooleanAttribute(key)))) { + + accum.append("=\""); + Entities.escape(accum, val == null ? EmptyString : val, out, true, false, false); + accum.append('"'); + } } } @@ -268,11 +331,13 @@ public String toString() { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof Attributes)) return false; + if (o == null || getClass() != o.getClass()) return false; Attributes that = (Attributes) o; - return !(attributes != null ? !attributes.equals(that.attributes) : that.attributes != null); + if (size != that.size) return false; + if (!Arrays.equals(keys, that.keys)) return false; + return Arrays.equals(vals, that.vals); } /** @@ -281,31 +346,40 @@ public boolean equals(Object o) { */ @Override public int hashCode() { - return attributes != null ? attributes.hashCode() : 0; + int result = size; + result = 31 * result + Arrays.hashCode(keys); + result = 31 * result + Arrays.hashCode(vals); + return result; } @Override public Attributes clone() { - if (attributes == null) - return new Attributes(); - Attributes clone; try { clone = (Attributes) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } - clone.attributes = new LinkedHashMap<>(attributes.size()); - for (Attribute attribute: this) - clone.attributes.put(attribute.getKey(), attribute.clone()); + clone.size = size; + keys = Arrays.copyOf(keys, size); + vals = Arrays.copyOf(vals, size); return clone; } - private class Dataset extends AbstractMap { + /** + * Internal method. Lowercases all keys. + */ + public void normalize() { + for (int i = 0; i < size; i++) { + keys[i] = lowerCase(keys[i]); + } + } + + private static class Dataset extends AbstractMap { + private final Attributes attributes; - private Dataset() { - if (attributes == null) - attributes = new LinkedHashMap<>(2); + private Dataset(Attributes attributes) { + this.attributes = attributes; } @Override @@ -316,9 +390,8 @@ public Set> entrySet() { @Override public String put(String key, String value) { String dataKey = dataKey(key); - String oldValue = hasKey(dataKey) ? attributes.get(dataKey).getValue() : null; - Attribute attr = new Attribute(dataKey, value); - attributes.put(dataKey, attr); + String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null; + attributes.put(dataKey, value); return oldValue; } @@ -329,7 +402,7 @@ public Iterator> iterator() { return new DatasetIterator(); } - @Override + @Override public int size() { int count = 0; Iterator iter = new DatasetIterator(); @@ -340,7 +413,7 @@ public int size() { } private class DatasetIterator implements Iterator> { - private Iterator attrIter = attributes.values().iterator(); + private Iterator attrIter = attributes.iterator(); private Attribute attr; public boolean hasNext() { while (attrIter.hasNext()) { diff --git a/src/main/java/org/jsoup/nodes/BooleanAttribute.java b/src/main/java/org/jsoup/nodes/BooleanAttribute.java index d3eb4efd6f..583ea4ff24 100644 --- a/src/main/java/org/jsoup/nodes/BooleanAttribute.java +++ b/src/main/java/org/jsoup/nodes/BooleanAttribute.java @@ -2,6 +2,7 @@ /** * A boolean attribute that is written out without any value. + * @deprecated just use null values (vs empty string) for booleans. */ public class BooleanAttribute extends Attribute { /** @@ -9,7 +10,7 @@ public class BooleanAttribute extends Attribute { * @param key attribute key */ public BooleanAttribute(String key) { - super(key, ""); + super(key, null); } @Override diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index a2448ba040..e31457c41c 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -372,13 +372,9 @@ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset; - // enables the doc to be shared in multiple threads, without creating new encoders on every traverse - private final ThreadLocal encoder = new ThreadLocal() { - @Override - protected CharsetEncoder initialValue() { - return charset.newEncoder(); - } - }; + CharsetEncoder encoder; // initialized by start of OuterHtmlVisitor and cleared at end + Entities.CoreCharset coreCharset; // fast encoders for ascii and utf8 + private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; @@ -430,7 +426,6 @@ public Charset charset() { */ public OutputSettings charset(Charset charset) { this.charset = charset; - encoder.remove(); return this; } @@ -444,15 +439,10 @@ public OutputSettings charset(String charset) { return this; } - CharsetEncoder encoder() { - CharsetEncoder ce = encoder.get(); - // check that the charset wasn't changed since accessed in this thread - // (this is probably overkill for something we're not advertising as threadsafe) - if (!ce.charset().equals(charset)) { - encoder.remove(); - ce = encoder.get(); // retrips initialValue() - } - return ce; + CharsetEncoder prepareEncoder() { + encoder = charset.newEncoder(); // created at start of OuterHtmlVisitor so each pass has own encoder, so OutputSettings can be shared among threads + coreCharset = Entities.CoreCharset.byName(encoder.charset().name()); + return encoder; } /** diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index 7d5d42f14a..ac4090753d 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.helper.StringUtil; +import org.jsoup.helper.Validate; import org.jsoup.nodes.Document.OutputSettings.Syntax; import java.io.IOException; @@ -25,6 +26,9 @@ public class DocumentType extends LeafNode { * @param systemId the doctype's system ID */ public DocumentType(String name, String publicId, String systemId) { + Validate.notNull(name); + Validate.notNull(publicId); + Validate.notNull(systemId); attr(NAME, name); attr(PUBLIC_ID, publicId); if (has(PUBLIC_ID)) { diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 266bd14cfb..83df6809a8 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1021,7 +1021,7 @@ public void head(Node node, int depth) { if (accum.length() > 0 && (element.isBlock() || element.tag.getName().equals("br")) && !TextNode.lastCharIsWhitespace(accum)) - accum.append(" "); + accum.append(' '); } } @@ -1300,7 +1300,7 @@ public Element val(String value) { return this; } - void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + void outerHtmlHead(final Appendable accum, int depth, final Document.OutputSettings out) throws IOException { if (out.prettyPrint() && (tag.formatAsBlock() || (parent() != null && parent().tag().formatAsBlock()) || out.outline())) { if (accum instanceof StringBuilder) { if (((StringBuilder) accum).length() > 0) @@ -1309,11 +1309,8 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr indent(accum, depth, out); } } - accum - .append("<") - .append(tagName()); - if (hasAttributes()) - attributes.html(accum, out); + accum.append('<').append(tagName()); + if (attributes != null) attributes.html(accum, out); // selfclosing includes unknown tags, isEmpty defines tags that are always empty if (childNodes.isEmpty() && tag.isSelfClosing()) { @@ -1323,7 +1320,7 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr accum.append(" />"); // in html, in xml } else - accum.append(">"); + accum.append('>'); } void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException { @@ -1332,7 +1329,7 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) thr tag.formatAsBlock() || (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && !(childNodes.get(0) instanceof TextNode)))) ))) indent(accum, depth, out); - accum.append(""); + accum.append("'); } } diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 1f7cc6b2b6..274d6fed96 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -161,8 +161,8 @@ static void escape(Appendable accum, String string, Document.OutputSettings out, boolean lastWasWhite = false; boolean reachedNonWhite = false; final EscapeMode escapeMode = out.escapeMode(); - final CharsetEncoder encoder = out.encoder(); - final CoreCharset coreCharset = CoreCharset.byName(encoder.charset().name()); + final CharsetEncoder encoder = out.encoder != null ? out.encoder : out.prepareEncoder(); + final CoreCharset coreCharset = out.coreCharset; // init in out.prepareEncoder() final int length = string.length(); int codePoint; @@ -278,10 +278,10 @@ private static boolean canEncode(final CoreCharset charset, final char c, final } } - private enum CoreCharset { + enum CoreCharset { ascii, utf, fallback; - private static CoreCharset byName(String name) { + static CoreCharset byName(final String name) { if (name.equals("US-ASCII")) return ascii; if (name.startsWith("UTF-")) // covers UTF-8, UTF-16, et al diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 9866e43faa..3fcfff942f 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -14,8 +14,6 @@ import java.util.LinkedList; import java.util.List; -import static org.jsoup.internal.Normalizer.lowerCase; - /** The base, abstract Node model. Elements, Documents, Comments etc are all Node instances. @@ -69,7 +67,7 @@ public String attr(String attributeKey) { String val = attributes().getIgnoreCase(attributeKey); if (val.length() > 0) return val; - else if (lowerCase(attributeKey).startsWith("abs:")) + else if (attributeKey.startsWith("abs:")) return absUrl(attributeKey.substring("abs:".length())); else return ""; } @@ -582,9 +580,9 @@ Document.OutputSettings getOutputSettings() { @param accum accumulator to place HTML into @throws IOException if appending to the given accumulator fails. */ - abstract void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException; + abstract void outerHtmlHead(final Appendable accum, int depth, final Document.OutputSettings out) throws IOException; - abstract void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException; + abstract void outerHtmlTail(final Appendable accum, int depth, final Document.OutputSettings out) throws IOException; /** * Write this node and its children to the given {@link Appendable}. @@ -602,7 +600,7 @@ public String toString() { } protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException { - accum.append("\n").append(StringUtil.padding(depth * out.indentAmount())); + accum.append('\n').append(StringUtil.padding(depth * out.indentAmount())); } /** @@ -688,6 +686,7 @@ private static class OuterHtmlVisitor implements NodeVisitor { OuterHtmlVisitor(Appendable accum, Document.OutputSettings out) { this.accum = accum; this.out = out; + out.prepareEncoder(); } public void head(Node node, int depth) { diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index e9df9dffd8..25a9b86e99 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -1,6 +1,5 @@ package org.jsoup.parser; -import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import static org.jsoup.internal.Normalizer.lowerCase; @@ -52,9 +51,7 @@ String normalizeAttribute(String name) { Attributes normalizeAttributes(Attributes attributes) { if (!preserveAttributeCase) { - for (Attribute attr : attributes) { - attr.setKey(lowerCase(attr.getKey())); - } + attributes.normalize(); } return attributes; } diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index c983557e82..567d7b29d1 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -1,9 +1,7 @@ package org.jsoup.parser; import org.jsoup.helper.Validate; -import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; -import org.jsoup.nodes.BooleanAttribute; import static org.jsoup.internal.Normalizer.lowerCase; @@ -107,15 +105,14 @@ final void newAttribute() { // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here pendingAttributeName = pendingAttributeName.trim(); if (pendingAttributeName.length() > 0) { - Attribute attribute; + String value; if (hasPendingAttributeValue) - attribute = new Attribute(pendingAttributeName, - pendingAttributeValue.length() > 0 ? pendingAttributeValue.toString() : pendingAttributeValueS); + value = pendingAttributeValue.length() > 0 ? pendingAttributeValue.toString() : pendingAttributeValueS; else if (hasEmptyAttributeValue) - attribute = new Attribute(pendingAttributeName, ""); + value = ""; else - attribute = new BooleanAttribute(pendingAttributeName); - attributes.put(attribute); + value = null; + attributes.put(pendingAttributeName, value); } } pendingAttributeName = null; diff --git a/src/test/java/org/jsoup/helper/StringUtilTest.java b/src/test/java/org/jsoup/helper/StringUtilTest.java index 29de2c9138..5af8c23863 100644 --- a/src/test/java/org/jsoup/helper/StringUtilTest.java +++ b/src/test/java/org/jsoup/helper/StringUtilTest.java @@ -23,6 +23,15 @@ public class StringUtilTest { assertEquals(" ", StringUtil.padding(1)); assertEquals(" ", StringUtil.padding(2)); assertEquals(" ", StringUtil.padding(15)); + assertEquals(" ", StringUtil.padding(45)); + } + + @Test public void paddingInACan() { + String[] padding = StringUtil.padding; + assertEquals(21, padding.length); + for (int i = 0; i < padding.length; i++) { + assertEquals(i, padding[i].length()); + } } @Test public void isBlank() { diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index cde3713185..b66f473fae 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import java.util.Iterator; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -30,8 +31,9 @@ public void html() { assertTrue(a.hasKeyIgnoreCase("tot")); assertEquals("There", a.getIgnoreCase("hEllo")); - assertEquals(1, a.dataset().size()); - assertEquals("Jsoup", a.dataset().get("name")); + Map dataset = a.dataset(); + assertEquals(1, dataset.size()); + assertEquals("Jsoup", dataset.get("name")); assertEquals("", a.get("tot")); assertEquals("a&p", a.get("Tot")); assertEquals("a&p", a.getIgnoreCase("tot")); @@ -46,11 +48,55 @@ public void testIteratorRemovable() { a.put("Tot", "a&p"); a.put("Hello", "There"); a.put("data-name", "Jsoup"); + assertTrue(a.hasKey("Tot")); Iterator iterator = a.iterator(); - iterator.next(); + Attribute attr = iterator.next(); + assertEquals("Tot", attr.getKey()); iterator.remove(); assertEquals(2, a.size()); + attr = iterator.next(); + assertEquals("Hello", attr.getKey()); + assertEquals("There", attr.getValue()); + + // make sure that's flowing to the underlying attributes object + assertEquals(2, a.size()); + assertEquals("There", a.get("Hello")); + assertFalse(a.hasKey("Tot")); + } + + @Test + public void testIteratorUpdateable() { + Attributes a = new Attributes(); + a.put("Tot", "a&p"); + a.put("Hello", "There"); + + assertFalse(a.hasKey("Foo")); + Iterator iterator = a.iterator(); + Attribute attr = iterator.next(); + attr.setKey("Foo"); + attr = iterator.next(); + attr.setKey("Bar"); + attr.setValue("Qux"); + + assertEquals("a&p", a.get("Foo")); + assertEquals("Qux", a.get("Bar")); + assertFalse(a.hasKey("Tot")); + assertFalse(a.hasKey("Hello")); + } + + @Test public void testIteratorHasNext() { + Attributes a = new Attributes(); + a.put("Tot", "1"); + a.put("Hello", "2"); + a.put("data-name", "3"); + + int seen = 0; + for (Attribute attribute : a) { + seen++; + assertEquals(String.valueOf(seen), attribute.getValue()); + } + assertEquals(3, seen); } @Test From 91f0354cf3090cd9e7720d36a93e5baf7b483334 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 4 Jul 2017 19:15:52 -0700 Subject: [PATCH 132/774] Attempt to trip Github's license detector Still MIT --- LICENSE | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index ab9f00b359..fe9cc78c98 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -© 2009-2017, Jonathan Hedley +Copyright (c) 2009-2017 Jonathan Hedley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 35e80a779b7908ddcd41a6a7df5f21b30bf999d2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 18 Jul 2017 22:57:37 -0700 Subject: [PATCH 133/774] Added bodyStream method --- CHANGES | 3 +++ src/main/java/org/jsoup/Connection.java | 11 ++++++++++- src/main/java/org/jsoup/helper/DataUtil.java | 2 +- src/main/java/org/jsoup/helper/HttpConnection.java | 9 +++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index aad87d711c..dd2595deda 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog into memory. + * Added Connection.Response.bodyStream(), a method to get the response body as an input stream. This is useful for + saving a large response straight to a file, without buffering fully into memory first. + * Performance improvements in text and HTML generation (through less GC). * Reduced memory consumption of text, scripts, and comments in the DOM by 40%, by refactoring the node diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 7651ed205f..8040886d2a 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -3,6 +3,7 @@ import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Proxy; @@ -673,10 +674,18 @@ interface Response extends Base { /** * Read the body of the response into a local buffer, so that {@link #parse()} may be called repeatedly on the * same connection response (otherwise, once the response is read, its InputStream will have been drained and - * may not be re-read). Calling {@link #body() } or {@link #bodyAsBytes()} has the same effect. If the requ + * may not be re-read). Calling {@link #body() } or {@link #bodyAsBytes()} has the same effect. * @return this response, for chaining */ Response bufferUp(); + + /** + * Get the body of the response as a (buffered) InputStream. You should close the input stream when you're done with it. + * Other body methods (like bufferUp, body, parse, etc) will not work in conjunction with this method. + *

This method is useful for writing large responses to disk, without buffering them completely into memory first.

+ * @return the response body input stream + */ + BufferedInputStream bodyStream(); } /** diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index b6724ca7a5..b243109362 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -177,7 +177,7 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr while (true) { read = inStream.read(buffer); if (read == -1) break; - if (capped) { + if (capped) { // todo - why not using ConstrainedInputStream? if (read >= remaining) { outStream.write(buffer, 0, remaining); break; diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index c5c032d1f6..3040039828 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -16,6 +16,7 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -820,6 +821,14 @@ public Connection.Response bufferUp() { return this; } + @Override + public BufferedInputStream bodyStream() { + Validate.isTrue(executed, "Request must be executed (with .execute(), .get(), or .post() before getting response body"); + Validate.isFalse(inputStreamRead, "Request has already been read"); + inputStreamRead = true; + return new ConstrainableInputStream(bodyStream, DataUtil.bufferSize, req.maxBodySize()); + } + // set up connection defaults, and details from request private static HttpURLConnection createConnection(Connection.Request req) throws IOException { final HttpURLConnection conn = (HttpURLConnection) ( From a7749a63871a7deccc33e66a22574e98264ce807 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 10 Aug 2017 23:58:26 +0200 Subject: [PATCH 134/774] Make consistency checks consistent Check for null first, check for emptyness after trimming. --- src/main/java/org/jsoup/nodes/Attribute.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index abe5d55b02..3163f9b9b0 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -59,8 +59,9 @@ public String getKey() { @param key the new key; must not be null */ public void setKey(String key) { - Validate.notEmpty(key); + Validate.notNull(key); key = key.trim(); + Validate.notEmpty(key); // trimming could potentially make empty, so validate here if (parent != null) { int i = parent.indexOfKey(this.key); if (i != Attributes.NotFound) From 590f77e561de567305c5992f6016f1e9af2178ca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 14:37:17 -0700 Subject: [PATCH 135/774] Fixed w3c test in JDK 9 The w3c dom output was indented, so needed to normalize. --- src/test/java/org/jsoup/TextUtil.java | 2 -- .../java/org/jsoup/helper/W3CDomTest.java | 24 +++++++------------ 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/jsoup/TextUtil.java b/src/test/java/org/jsoup/TextUtil.java index 5035d26eef..735714b8b3 100644 --- a/src/test/java/org/jsoup/TextUtil.java +++ b/src/test/java/org/jsoup/TextUtil.java @@ -5,8 +5,6 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextUtil { - public static final String LE = String.format("%n"); - public static String stripNewlines(String text) { text = text.replaceAll("\\n\\s*", ""); return text; diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index bb3e284504..31a854aa3a 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -1,6 +1,7 @@ package org.jsoup.helper; import org.jsoup.Jsoup; +import org.jsoup.TextUtil; import org.jsoup.integration.ParseTest; import org.jsoup.nodes.Element; import org.junit.Test; @@ -10,7 +11,6 @@ import java.io.File; import java.io.IOException; -import static org.jsoup.TextUtil.LE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -22,21 +22,13 @@ public void simpleConversion() { W3CDom w3c = new W3CDom(); Document wDoc = w3c.fromJsoup(doc); - String out = w3c.asString(wDoc); - assertEquals( - "" + LE + - "" + LE + - "" + LE + - "W3c" + LE + - " " + LE + - "" + LE + - "

Text

" + LE + - "" + LE + - "What" + LE + - "" + LE + - "" + LE + - "" + LE - , out); + String out = TextUtil.stripNewlines(w3c.asString(wDoc)); + String expected = TextUtil.stripNewlines( + "W3c" + + "

Text

What" + + "" + ); + assertEquals(expected, out); } @Test From be56a5469f1d98094f0f950948dc3aa72aede159 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:00:11 -0700 Subject: [PATCH 136/774] Updated javadoc plugin to fix bug with JDK9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fd8fba36d4..c3cae90a60 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 3.0.0-M1 -Xdoclint:none From cd3e51d1dcfcf3c47e5146434ddbbed9ff40d823 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:10:44 -0700 Subject: [PATCH 137/774] Version comment --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3cae90a60..eabc9664eb 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ - + org.codehaus.mojo animal-sniffer-maven-plugin 1.15 From 397a0caeb374c55b8dcb58e09d0faebb6e017252 Mon Sep 17 00:00:00 2001 From: MazzeX28 <31850149+MazzeX28@users.noreply.github.com> Date: Sun, 8 Oct 2017 00:15:05 +0200 Subject: [PATCH 138/774] [fix] parsing of mixed-case tags after b934c5d (#942) --- src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index bb2a61e9da..aaf60415ba 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -760,7 +760,7 @@ else if (!tb.onStack(formatEl)) { } boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { - String name = t.asEndTag().name(); // matches with case sensitivity if enabled + String name = tb.settings.normalizeTag(t.asEndTag().name()); // matches with case sensitivity if enabled ArrayList stack = tb.getStack(); for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index fa665e2fce..c8457588f2 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1005,6 +1005,13 @@ public void testInvalidTableContents() throws IOException { assertEquals(" A B ", StringUtil.normaliseWhitespace(doc.body().html())); } + @Test public void caseInsensitiveParseTree() { + String html = "AB"; + Parser parser = Parser.htmlParser(); + Document doc = parser.parseInput(html, ""); + assertEquals(" A B ", StringUtil.normaliseWhitespace(doc.body().html())); + } + @Test public void selfClosingVoidIsNotAnError() { String html = "

test
test

"; Parser parser = Parser.htmlParser().setTrackErrors(5); From 6759c78198c63507b5e2c5451bf088d2e254702c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:18:37 -0700 Subject: [PATCH 139/774] Changelog foe #942 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index dd2595deda..b35a0bf9ed 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,9 @@ jsoup changelog incorrectly parsed as data or text. + * Bugfix: fixed an issue with unknown mixed-case tags + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: From b185fd76845a9c99e4077bc017d25d65ed58d440 Mon Sep 17 00:00:00 2001 From: xiaojian cai Date: Sun, 8 Oct 2017 06:23:00 +0800 Subject: [PATCH 140/774] patch about issue #836 addChildren is quadratic (#930) * patch about issue #836 addChildren is quadratic patch about issue #836 addChildren is quadratic when add many children, the origin implementation will move childNodes array per loop.(method add(index, obj)), a performance improvement was made using addAll(index, objs).there are three steps: 1. reparent all the children 2. use addAll(index, objs) to insert children 3. reset sibling index it is very similar with method addChildren(Node... children) --- src/main/java/org/jsoup/nodes/Node.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 3fcfff942f..e4ae21f3b4 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -8,11 +8,7 @@ import org.jsoup.select.NodeVisitor; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** The base, abstract Node model. Elements, Documents, Comments etc are all Node instances. @@ -460,12 +456,11 @@ protected void addChildren(int index, Node... children) { Validate.noNullElements(children); final List nodes = ensureChildNodes(); - for (int i = children.length - 1; i >= 0; i--) { - Node in = children[i]; - reparentChild(in); - nodes.add(index, in); - reindexChildren(index); + for (Node child : children) { + reparentChild(child); } + nodes.addAll(index, Arrays.asList(children)); + reindexChildren(index); } protected void reparentChild(Node child) { From 846108dcdfc2c884c298c5dde2ed05eda9e2fb13 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:25:25 -0700 Subject: [PATCH 141/774] Changelog for #930 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index b35a0bf9ed..0a7f83e354 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,9 @@ jsoup changelog * Updated Element.text() and the :contains(text) selector to consider   character as spaces. + * Improved performance of Node.addChildren (was quadratic) + + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. From c33ba536da04e5b22a1c2740ce38761300dd13a9 Mon Sep 17 00:00:00 2001 From: xiaojian cai Date: Sun, 8 Oct 2017 06:27:26 +0800 Subject: [PATCH 142/774] fixed issue #916 (#928) * fixed issue #916 fixed issue #916 InputStream not closed in class org.jsoup.nodes.Entities. use jdk7 try-with-resources to close "BufferReader input". * fixed issue #916 try-with-resources makes use of Throwable.addSuppressed(Throwable) which was officially added with API 19. using normal try-catch block instead. --- src/main/java/org/jsoup/nodes/Entities.java | 73 ++++++++++++--------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 274d6fed96..f0d37a0a4c 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -303,41 +303,52 @@ private static void load(EscapeMode e, String file, int size) { throw new IllegalStateException("Could not read resource " + file + ". Make sure you copy resources for " + Entities.class.getCanonicalName()); int i = 0; - BufferedReader input = new BufferedReader(new InputStreamReader(stream, ASCII)); - CharacterReader reader = new CharacterReader(input); - - while (!reader.isEmpty()) { - // NotNestedLessLess=10913,824;1887 - - final String name = reader.consumeTo('='); - reader.advance(); - final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); - final char codeDelim = reader.current(); - reader.advance(); - final int cp2; - if (codeDelim == ',') { - cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); + BufferedReader input = null; + try { + input = new BufferedReader(new InputStreamReader(stream, ASCII)); + CharacterReader reader = new CharacterReader(input); + + while (!reader.isEmpty()) { + // NotNestedLessLess=10913,824;1887 + + final String name = reader.consumeTo('='); + reader.advance(); + final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); + final char codeDelim = reader.current(); + reader.advance(); + final int cp2; + if (codeDelim == ',') { + cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); + reader.advance(); + } else { + cp2 = empty; + } + String indexS = reader.consumeTo('\n'); + // default git checkout on windows will add a \r there, so remove + if (indexS.charAt(indexS.length() - 1) == '\r') { + indexS = indexS.substring(0, indexS.length() - 1); + } + final int index = Integer.parseInt(indexS, codepointRadix); reader.advance(); - } else { - cp2 = empty; - } - String indexS = reader.consumeTo('\n'); - // default git checkout on windows will add a \r there, so remove - if (indexS.charAt(indexS.length() - 1) == '\r') { - indexS = indexS.substring(0, indexS.length() - 1); - } - final int index = Integer.parseInt(indexS, codepointRadix); - reader.advance(); - e.nameKeys[i] = name; - e.codeVals[i] = cp1; - e.codeKeys[index] = cp1; - e.nameVals[index] = name; + e.nameKeys[i] = name; + e.codeVals[i] = cp1; + e.codeKeys[index] = cp1; + e.nameVals[index] = name; - if (cp2 != empty) { - multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); + if (cp2 != empty) { + multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); + } + i++; + } + } finally { + try { + if (input != null) { + input.close(); + } + } catch (IOException e1) { + //ignore exception } - i++; } Validate.isTrue(i == size, "Unexpected count of entities loaded for " + file); } From 8c7350baf1b8def5ca18526ef68570b24b036a0a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:29:24 -0700 Subject: [PATCH 143/774] Changelog for #928 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 0a7f83e354..68ed08fb42 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,9 @@ jsoup changelog * Bugfix: fixed an issue with unknown mixed-case tags + * Bugfix: fixed an issue where the entity resources were left open after startup, causing a warning. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: From ac13ffd28c09afa1da96b60e2e41f646de87d4af Mon Sep 17 00:00:00 2001 From: Ioana Leontiuc Date: Sun, 8 Oct 2017 00:31:29 +0200 Subject: [PATCH 144/774] Missing Test Case to preserve consumeToIgnoreCase() behavior (#925) Since pos is a class attribute, calling consumeToIgnoreCase() on the same queue will return different Strings. If this is the intended behavior than this test preserves that. If not the second assert should be changed to assertEquals(" third "; + TokenQueue tq = new TokenQueue(t); + String data = tq.chompToIgnoreCase(""); + assertEquals(""); + assertEquals(" third ", data); + } + } From b27c4c14aa90c23625b105d3140fe2bab42c1cd0 Mon Sep 17 00:00:00 2001 From: Ioana Leontiuc Date: Sun, 8 Oct 2017 00:33:28 +0200 Subject: [PATCH 145/774] Missing Test Case for whitespace class name (#924) The method that checks if an element has a class is never tested for a class name that ends with a whitespace. The implemented functionality is correct however if the "if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) {" check is removed, no tests catch this. The missing test case was discovered through mutation testing analysis. --- src/test/java/org/jsoup/nodes/ElementTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index e69ebe47e6..4391ef440e 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1165,4 +1165,20 @@ public void testAppendTo() { assertEquals("p", matched.nodeName()); assertTrue(matched.is(":containsOwn(get what you want)")); } + + + + @Test + public void whiteSpaceClassElement(){ + Tag tag = Tag.valueOf("a"); + Attributes attribs = new Attributes(); + Element el = new Element(tag, "", attribs); + + attribs.put("class", "abc "); + boolean hasClass = el.hasClass("ab"); + assertFalse(hasClass); + + } + + } From 410c15f17dd04588f9f402c8d27cb7a6843f0da7 Mon Sep 17 00:00:00 2001 From: Ioana Leontiuc Date: Sun, 8 Oct 2017 00:34:11 +0200 Subject: [PATCH 146/774] Duplicated Logic in Called Method (#923) The same logic is implemented in setParentNode(Node parentNode). --- src/main/java/org/jsoup/nodes/Node.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index e4ae21f3b4..24ac705e97 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -464,8 +464,6 @@ protected void addChildren(int index, Node... children) { } protected void reparentChild(Node child) { - if (child.parentNode != null) - child.parentNode.removeChild(child); child.setParentNode(this); } From bbcf905c8228374c733e45144119582d4758ce11 Mon Sep 17 00:00:00 2001 From: Ioana Leontiuc Date: Sun, 8 Oct 2017 00:34:43 +0200 Subject: [PATCH 147/774] Missing Test Case for location conversion (#922) By running mutation testing analysis I discovered that when a jsoup document is being converted into a W3C document the location parsing is never tested. To this end we propose another test case. --- src/test/java/org/jsoup/helper/W3CDomTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 31a854aa3a..983816257e 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -46,6 +46,21 @@ public void convertsGoogle() throws IOException { String out = w3c.asString(wDoc); assertTrue(out.contains("ipod")); } + + + @Test + public void convertsGoogleLocation() throws IOException { + File in = ParseTest.getFile("/htmltests/google-ipod.html"); + org.jsoup.nodes.Document doc = Jsoup.parse(in, "UTF8"); + + W3CDom w3c = new W3CDom(); + Document wDoc = w3c.fromJsoup(doc); + + String out = w3c.asString(wDoc); + assertEquals(doc.location(), wDoc.getDocumentURI() ); + } + + @Test public void namespacePreservation() throws IOException { From 51163017325cf175cda594faed991c05e9fdbe77 Mon Sep 17 00:00:00 2001 From: damonwong93 Date: Sat, 7 Oct 2017 15:37:28 -0700 Subject: [PATCH 148/774] Fixed IndexLessThan matches() to ignore root element and added tests (#918) * Fixed IndexLessThan matches() to ignore root element and added tests * moved unit tests for getElementsByIndexLessThan() and getElementsByIndexGreaterThan() to ElementTest.java --- src/main/java/org/jsoup/select/Evaluator.java | 2 +- .../java/org/jsoup/nodes/ElementTest.java | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index e797495eaf..b6670d4231 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -340,7 +340,7 @@ public IndexLessThan(int index) { @Override public boolean matches(Element root, Element element) { - return element.elementSiblingIndex() < index; + return root != element && element.elementSiblingIndex() < index; } @Override diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 4391ef440e..bb49d25f41 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1166,7 +1166,39 @@ public void testAppendTo() { assertTrue(matched.is(":containsOwn(get what you want)")); } + @Test + public void testRemoveBeforeIndex() { + Document doc = Jsoup.parse( + "

before1

before2

XXX

after1

after2

", + ""); + Element body = doc.select("body").first(); + Elements elems = body.select("p:matchesOwn(XXX)"); + Element xElem = elems.first(); + Elements beforeX = xElem.parent().getElementsByIndexLessThan(xElem.elementSiblingIndex()); + + for(Element p : beforeX) { + p.remove(); + } + + assertEquals("

XXX

after1

after2

", TextUtil.stripNewlines(body.outerHtml())); + } + @Test + public void testRemoveAfterIndex() { + Document doc2 = Jsoup.parse( + "

before1

before2

XXX

after1

after2

", + ""); + Element body = doc2.select("body").first(); + Elements elems = body.select("p:matchesOwn(XXX)"); + Element xElem = elems.first(); + Elements afterX = xElem.parent().getElementsByIndexGreaterThan(xElem.elementSiblingIndex()); + + for(Element p : afterX) { + p.remove(); + } + + assertEquals("

before1

before2

XXX

", TextUtil.stripNewlines(body.outerHtml())); + } @Test public void whiteSpaceClassElement(){ @@ -1179,6 +1211,4 @@ public void whiteSpaceClassElement(){ assertFalse(hasClass); } - - } From 929c2f8bcc6b504715355afd547f1e3551590c00 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:38:47 -0700 Subject: [PATCH 149/774] Changelog for #918 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 68ed08fb42..4a243ddcbc 100644 --- a/CHANGES +++ b/CHANGES @@ -54,6 +54,9 @@ jsoup changelog * Bugfix: fixed an issue where the entity resources were left open after startup, causing a warning. + * Bugfix: fixed an issue where Element.getElementsByIndexLessThan(index) would incorrectly provide the root element + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: From 32af6ba9f1b15f89660645a7a27f89517a0890c6 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Sun, 8 Oct 2017 00:43:46 +0200 Subject: [PATCH 150/774] Add unit test (but no fix) for 883 (#893) --- src/test/java/org/jsoup/nodes/AttributesTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index b66f473fae..b331636099 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -145,4 +145,14 @@ public void removeCaseSensitive() { assertFalse(a.hasKey("Tot")); } + @Test + public void testSetKeyConsistency() { + Attributes a = new Attributes(); + a.put("a", "a"); + for(Attribute at : a) { + at.setKey("b"); + } + assertFalse("Attribute 'a' not correctly removed", a.hasKey("a")); + assertTrue("Attribute 'b' not present after renaming", a.hasKey("b")); + } } From 4fa93397353a7cd3cf15f9c29c40f8a18fc051e5 Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Sat, 7 Oct 2017 18:46:27 -0400 Subject: [PATCH 151/774] allow template inside thead/tbody/tr. Fixes #807 (#901) --- .../org/jsoup/parser/HtmlTreeBuilder.java | 4 +-- .../jsoup/parser/HtmlTreeBuilderState.java | 8 +++-- .../java/org/jsoup/parser/HtmlParserTest.java | 11 +++++++ .../htmltests/table-polymer-template.html | 29 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/htmltests/table-polymer-template.html diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 6ada343afc..041dcbd9b5 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -357,11 +357,11 @@ void clearStackToTableContext() { } void clearStackToTableBodyContext() { - clearStackToContext("tbody", "tfoot", "thead"); + clearStackToContext("tbody", "tfoot", "thead", "template"); } void clearStackToTableRowContext() { - clearStackToContext("tr"); + clearStackToContext("tr", "template"); } private void clearStackToContext(String... nodeNames) { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index aaf60415ba..c4924e42bc 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1033,7 +1033,9 @@ boolean process(Token t, HtmlTreeBuilder tb) { case StartTag: Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); - if (name.equals("tr")) { + if (name.equals("template")) { + tb.insert(startTag); + } else if (name.equals("tr")) { tb.clearStackToTableBodyContext(); tb.insert(startTag); tb.transition(InRow); @@ -1093,7 +1095,9 @@ boolean process(Token t, HtmlTreeBuilder tb) { Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); - if (StringUtil.in(name, "th", "td")) { + if (name.equals("template")) { + tb.insert(startTag); + } else if (StringUtil.in(name, "th", "td")) { tb.clearStackToTableRowContext(); tb.insert(startTag); tb.transition(InCell); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index c8457588f2..a2cb069efc 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1034,4 +1034,15 @@ public void testInvalidTableContents() throws IOException { String clean = Jsoup.clean(html, Whitelist.relaxed()); assertEquals("

test

Two
", StringUtil.normaliseWhitespace(clean)); } + + @Test public void testTemplateInsideTable() throws IOException { + File in = ParseTest.getFile("/htmltests/table-polymer-template.html"); + Document doc = Jsoup.parse(in, "UTF-8"); + doc.outputSettings().prettyPrint(true); + + Elements templates = doc.body().getElementsByTag("template"); + for (Element template : templates) { + assertTrue(template.childNodes().size() > 1); + } + } } diff --git a/src/test/resources/htmltests/table-polymer-template.html b/src/test/resources/htmltests/table-polymer-template.html new file mode 100644 index 0000000000..97a23e48cb --- /dev/null +++ b/src/test/resources/htmltests/table-polymer-template.html @@ -0,0 +1,29 @@ + + + + + + + + + +
From ecfcd44e33377b120fee09effea7ba3c80af3964 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:47:54 -0700 Subject: [PATCH 152/774] Changelog for #901 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 4a243ddcbc..afe3b344b0 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,9 @@ jsoup changelog * Improved performance of Node.addChildren (was quadratic) + * Added missing support for template tags in tables + + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. From 925ecb3b9c87cf4ac024c6b2a8301083fd7b9175 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 15:58:13 -0700 Subject: [PATCH 153/774] Travis can't support OracleJDK7 any more Because Oracle. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4337408f63..5d35860d78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ language: java jdk: - openjdk7 - - oraclejdk7 - oraclejdk8 + - oraclejdk9 cache: directories: From 45111a152d6568c38c268b6460045ad60f7cdeff Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Oct 2017 16:45:00 -0700 Subject: [PATCH 154/774] Ensure Android 8 compatibility Fixes #948 --- pom.xml | 7 ++++++- src/main/java/org/jsoup/nodes/Attributes.java | 16 ++++++++++++---- src/main/java/org/jsoup/nodes/Document.java | 3 +-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index eabc9664eb..e4135151a5 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.15 + 1.16 animal-sniffer @@ -61,6 +61,11 @@ java17 1.0 + + net.sf.androidscents.signature + android-api-level-8 + 2.2_r3 + diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index c8c1d0751e..a52bcdad17 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -54,8 +54,16 @@ private void checkCapacity(int minNewSize) { if (minNewSize > newSize) newSize = minNewSize; - keys = Arrays.copyOf(keys, newSize); - vals = Arrays.copyOf(vals, newSize); + keys = copyOf(keys, newSize); + vals = copyOf(vals, newSize); + } + + // simple implementation of Arrays.copy, for support of Android API 8. + private static String[] copyOf(String[] orig, int size) { + final String[] copy = new String[size]; + System.arraycopy(orig, 0, copy, 0, + Math.min(orig.length, size)); + return copy; } int indexOfKey(String key) { @@ -361,8 +369,8 @@ public Attributes clone() { throw new RuntimeException(e); } clone.size = size; - keys = Arrays.copyOf(keys, size); - vals = Arrays.copyOf(vals, size); + keys = copyOf(keys, size); + vals = copyOf(vals, size); return clone; } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index e31457c41c..2f1c03ff78 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -8,7 +8,6 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -381,7 +380,7 @@ public enum Syntax {html, xml} private Syntax syntax = Syntax.html; public OutputSettings() { - charset(StandardCharsets.UTF_8); + charset(Charset.forName("UTF8")); } /** From 3a81df16aae71eef82e0074cb6e8953aea33dbb0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 15:30:18 -0700 Subject: [PATCH 155/774] Strip CR, so it works on Windows --- src/test/java/org/jsoup/TextUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/TextUtil.java b/src/test/java/org/jsoup/TextUtil.java index 735714b8b3..922f4c77d1 100644 --- a/src/test/java/org/jsoup/TextUtil.java +++ b/src/test/java/org/jsoup/TextUtil.java @@ -6,7 +6,7 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextUtil { public static String stripNewlines(String text) { - text = text.replaceAll("\\n\\s*", ""); + text = text.replaceAll("\\r?\\n\\s*", ""); return text; } } From 655e3aab5ad477a790f967f563b8d41c1267475b Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Tue, 21 Mar 2017 15:42:55 +0100 Subject: [PATCH 156/774] Allow `static` use of NodeTraversor.traverse() --- src/main/java/org/jsoup/select/NodeTraversor.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index 36a585a96c..902a00550d 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -24,6 +24,15 @@ public NodeTraversor(NodeVisitor visitor) { * @param root the root node point to traverse. */ public void traverse(Node root) { + traverse(visitor, root); + } + + /** + * Start a depth-first traverse of the root and all of its descendants. + * @param visitor Node visitor. + * @param root the root node point to traverse. + */ + public static void traverse(NodeVisitor visitor, Node root) { Node node = root; int depth = 0; From 6a284f14e1ddf238cbdab0cbf4e1c33dd5b81566 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Wed, 22 Mar 2017 11:32:58 +0100 Subject: [PATCH 157/774] Add `traverse(NodeVisitor, Elements)`. --- src/main/java/org/jsoup/select/NodeTraversor.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index 902a00550d..d0df87e9be 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -54,4 +54,16 @@ public static void traverse(NodeVisitor visitor, Node root) { } } } + + /** + * Start a depth-first traverse of all elements. + * @param visitor Node visitor. + * @param elements Elements to filter. + */ + public static void traverse(NodeVisitor visitor, Elements elements) { + Validate.notNull(visitor); + Validate.notNull(elements); + for (Element el : elements) + traverse(visitor, el); + } } From 6652240656471a34544ccc1a8c337408cd235a46 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 23 Mar 2017 17:16:26 +0100 Subject: [PATCH 158/774] Use static NodeTraversor.traverse --- src/main/java/org/jsoup/examples/HtmlToPlainText.java | 3 +-- src/main/java/org/jsoup/helper/W3CDom.java | 3 +-- src/main/java/org/jsoup/nodes/Element.java | 6 +++--- src/main/java/org/jsoup/nodes/Node.java | 5 ++--- src/main/java/org/jsoup/safety/Cleaner.java | 3 +-- src/main/java/org/jsoup/select/Collector.java | 2 +- src/main/java/org/jsoup/select/Elements.java | 6 +----- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/jsoup/examples/HtmlToPlainText.java b/src/main/java/org/jsoup/examples/HtmlToPlainText.java index b9a49b82e7..89ca399095 100644 --- a/src/main/java/org/jsoup/examples/HtmlToPlainText.java +++ b/src/main/java/org/jsoup/examples/HtmlToPlainText.java @@ -60,8 +60,7 @@ public static void main(String... args) throws IOException { */ public String getPlainText(Element element) { FormattingVisitor formatter = new FormattingVisitor(); - NodeTraversor traversor = new NodeTraversor(formatter); - traversor.traverse(element); // walk the DOM, and call .head() and .tail() for each node + NodeTraversor.traverse(formatter, element); // walk the DOM, and call .head() and .tail() for each node return formatter.toString(); } diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index c0235d2cdb..ac71e59bce 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -59,8 +59,7 @@ public void convert(org.jsoup.nodes.Document in, Document out) { out.setDocumentURI(in.location()); org.jsoup.nodes.Element rootEl = in.child(0); // skip the #root node - NodeTraversor traversor = new NodeTraversor(new W3CBuilder(out)); - traversor.traverse(rootEl); + NodeTraversor.traverse(new W3CBuilder(out), rootEl); } /** diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 83df6809a8..cb22f74951 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1010,8 +1010,8 @@ public Elements getAllElements() { * @see #textNodes() */ public String text() { - final StringBuilder accum = StringUtil.stringBuilder(); - new NodeTraversor(new NodeVisitor() { + final StringBuilder accum = new StringBuilder(); + NodeTraversor.traverse(new NodeVisitor() { public void head(Node node, int depth) { if (node instanceof TextNode) { TextNode textNode = (TextNode) node; @@ -1027,7 +1027,7 @@ public void head(Node node, int depth) { public void tail(Node node, int depth) { } - }).traverse(this); + }, this); return accum.toString().trim(); } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 24ac705e97..1e40852c76 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -543,8 +543,7 @@ protected void setSiblingIndex(int siblingIndex) { */ public Node traverse(NodeVisitor nodeVisitor) { Validate.notNull(nodeVisitor); - NodeTraversor traversor = new NodeTraversor(nodeVisitor); - traversor.traverse(this); + NodeTraversor.traverse(nodeVisitor, this); return this; } @@ -559,7 +558,7 @@ public String outerHtml() { } protected void outerHtml(Appendable accum) { - new NodeTraversor(new OuterHtmlVisitor(accum, getOutputSettings())).traverse(this); + NodeTraversor.traverse(new OuterHtmlVisitor(accum, getOutputSettings()), this); } // if this node has no document (or parent), retrieve the default output settings diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 34f0b14184..0ac5edb0f9 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -139,8 +139,7 @@ public void tail(Node source, int depth) { private int copySafeNodes(Element source, Element dest) { CleaningVisitor cleaningVisitor = new CleaningVisitor(source, dest); - NodeTraversor traversor = new NodeTraversor(cleaningVisitor); - traversor.traverse(source); + NodeTraversor.traverse(cleaningVisitor, source); return cleaningVisitor.numDiscarded; } diff --git a/src/main/java/org/jsoup/select/Collector.java b/src/main/java/org/jsoup/select/Collector.java index 8f01045768..a28d3b641a 100644 --- a/src/main/java/org/jsoup/select/Collector.java +++ b/src/main/java/org/jsoup/select/Collector.java @@ -21,7 +21,7 @@ private Collector() { */ public static Elements collect (Evaluator eval, Element root) { Elements elements = new Elements(); - new NodeTraversor(new Accumulator(root, elements, eval)).traverse(root); + NodeTraversor.traverse(new Accumulator(root, elements, eval), root); return elements; } diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 9b100c79ab..c9a079490d 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -612,11 +612,7 @@ public Element last() { * @return this, for chaining */ public Elements traverse(NodeVisitor nodeVisitor) { - Validate.notNull(nodeVisitor); - NodeTraversor traversor = new NodeTraversor(nodeVisitor); - for (Element el: this) { - traversor.traverse(el); - } + NodeTraversor.traverse(nodeVisitor, this); return this; } From ad23b481e4c40fcc73c1fa59513b5a7ab0e271d6 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 23 Mar 2017 17:18:27 +0100 Subject: [PATCH 159/774] Add new NodeFilter interface to NodeTraversor --- .../java/org/jsoup/select/NodeFilter.java | 58 ++++++++++++++++ .../java/org/jsoup/select/NodeTraversor.java | 68 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/main/java/org/jsoup/select/NodeFilter.java diff --git a/src/main/java/org/jsoup/select/NodeFilter.java b/src/main/java/org/jsoup/select/NodeFilter.java new file mode 100644 index 0000000000..821f98d2aa --- /dev/null +++ b/src/main/java/org/jsoup/select/NodeFilter.java @@ -0,0 +1,58 @@ +package org.jsoup.select; + +import org.jsoup.nodes.Node; + +/** + * Node filter interface. Provide an implementing class to {@link NodeTraversor} to iterate through nodes. + *

+ * This interface provides two methods, {@code head} and {@code tail}. The head method is called when the node is first + * seen, and the tail method when all of the node's children have been visited. As an example, head can be used to + * create a start tag for a node, and tail to create the end tag. + *

+ *

+ * For every node, the filter has to decide whether to + *

    + *
  • continue ({@link FilterResult#CONTINUE}),
  • + *
  • skip all children ({@link FilterResult#SKIP_CHILDREN}),
  • + *
  • skip node entirely ({@link FilterResult#SKIP_ENTIRELY}),
  • + *
  • remove the subtree ({@link FilterResult#REMOVE}),
  • + *
  • interrupt the iteration and return ({@link FilterResult#STOP}).
  • + *
+ * The difference between {@link FilterResult#SKIP_CHILDREN} and {@link FilterResult#SKIP_ENTIRELY} is that the first + * will invoke {@link NodeFilter#tail(Node, int)} on the node, while the latter will not. + * Within {@link NodeFilter#tail(Node, int)}, both are equivalent to {@link FilterResult#CONTINUE}. + *

+ */ +public interface NodeFilter { + /** + * Filter decision. + */ + public enum FilterResult { + /** Continue processing the tree */ + CONTINUE, + /** Skip the child nodes, but do call {@link NodeFilter#tail(Node, int)} next. */ + SKIP_CHILDREN, + /** Skip the subtree, and do not call {@link NodeFilter#tail(Node, int)}. */ + SKIP_ENTIRELY, + /** Remove the node and its children */ + REMOVE, + /** Stop processing */ + STOP + } + + /** + * Callback for when a node is first visited. + * @param node the node being visited. + * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1. + * @return Filter decision + */ + FilterResult head(Node node, int depth); + + /** + * Callback for when a node is last visited, after all of its descendants have been visited. + * @param node the node being visited. + * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1. + * @return Filter decision + */ + FilterResult tail(Node node, int depth); +} diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index d0df87e9be..a732245f98 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -1,6 +1,9 @@ package org.jsoup.select; +import org.jsoup.helper.Validate; +import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import org.jsoup.select.NodeFilter.FilterResult; /** * Depth-first node traversor. Use to iterate through all nodes under and including the specified root node. @@ -66,4 +69,69 @@ public static void traverse(NodeVisitor visitor, Elements elements) { for (Element el : elements) traverse(visitor, el); } + + /** + * Start a depth-first filtering of the root and all of its descendants. + * @param filter Node visitor. + * @param root the root node point to traverse. + * @return The filter result of the root node, or {@link FilterResult#STOP}. + */ + public static FilterResult filter(NodeFilter filter, Node root) { + Node node = root; + int depth = 0; + + while (node != null) { + FilterResult result = filter.head(node, depth); + if (result == FilterResult.STOP) + return result; + // Descend into child nodes: + if (result == FilterResult.CONTINUE && node.childNodeSize() > 0) { + node = node.childNode(0); + ++depth; + continue; + } + // No siblings, move upwards: + while (node.nextSibling() == null && depth > 0) { + // 'tail' current node: + if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { + result = filter.tail(node, depth); + if (result == FilterResult.STOP) + return result; + } + Node prev = node; // In case we need to remove it below. + node = node.parentNode(); + depth--; + if (result == FilterResult.REMOVE) + prev.remove(); // Remove AFTER finding parent. + result = FilterResult.CONTINUE; // Parent was not pruned. + } + // 'tail' current node, then proceed with siblings: + if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { + result = filter.tail(node, depth); + if (result == FilterResult.STOP) + return result; + } + if (node == root) + return result; + Node prev = node; // In case we need to remove it below. + node = node.nextSibling(); + if (result == FilterResult.REMOVE) + prev.remove(); // Remove AFTER finding sibling. + } + // root == null? + return FilterResult.CONTINUE; + } + + /** + * Start a depth-first filtering of all elements. + * @param filter Node filter. + * @param elements Elements to filter. + */ + public static void filter(NodeFilter filter, Elements elements) { + Validate.notNull(filter); + Validate.notNull(elements); + for (Element el : elements) + if (filter(filter, el) == FilterResult.STOP) + break; + } } From fe001bcccd29f9659ff375a625fd575c9285ce86 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 23 Mar 2017 17:19:22 +0100 Subject: [PATCH 160/774] Add Node.filter, Elements.filter convenience API --- src/main/java/org/jsoup/nodes/Node.java | 12 ++++++++++++ src/main/java/org/jsoup/select/Elements.java | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 1e40852c76..cdba9061e7 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -4,6 +4,7 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.Parser; +import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; @@ -547,6 +548,17 @@ public Node traverse(NodeVisitor nodeVisitor) { return this; } + /** + * Perform a depth-first filtering through this node and its descendants. + * @param nodeFilter the filter callbacks to perform on each node + * @return this node, for chaining + */ + public Node filter(NodeFilter nodeFilter) { + Validate.notNull(nodeFilter); + NodeTraversor.filter(nodeFilter, this); + return this; + } + /** Get the outer HTML of this node. @return HTML diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index c9a079490d..fcf1ee019d 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -616,6 +616,16 @@ public Elements traverse(NodeVisitor nodeVisitor) { return this; } + /** + * Perform a depth-first filtering on each of the selected elements. + * @param nodeFilter the filter callbacks to perform on each node + * @return this, for chaining + */ + public Elements filter(NodeFilter nodeFilter) { + NodeTraversor.filter(nodeFilter, this); + return this; + } + /** * Get the {@link FormElement} forms from the selected elements, if any. * @return a list of {@link FormElement}s pulled from the matched elements. The list will be empty if the elements contain From 0d714da40f8d13156025d3bec7daa9746d276490 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 23 Mar 2017 17:19:36 +0100 Subject: [PATCH 161/774] Add unit test. --- .../java/org/jsoup/select/TraversorTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/test/java/org/jsoup/select/TraversorTest.java diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java new file mode 100644 index 0000000000..53b52198e1 --- /dev/null +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -0,0 +1,107 @@ +package org.jsoup.select; + +import static org.junit.Assert.assertEquals; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Node; +import org.junit.Test; + +public class TraversorTest { + // Note: NodeTraversor.traverse(new NodeVisitor) is tested in + // ElementsTest#traverse() + + @Test + public void filterVisit() { + Document doc = Jsoup.parse("

Hello

There
"); + final StringBuilder accum = new StringBuilder(); + NodeTraversor.filter(new NodeFilter() { + public FilterResult head(Node node, int depth) { + accum.append("<" + node.nodeName() + ">"); + return FilterResult.CONTINUE; + } + + public FilterResult tail(Node node, int depth) { + accum.append(""); + return FilterResult.CONTINUE; + } + }, doc.select("div")); + assertEquals("

<#text>

<#text>
", accum.toString()); + } + + @Test + public void filterSkipChildren() { + Document doc = Jsoup.parse("

Hello

There
"); + final StringBuilder accum = new StringBuilder(); + NodeTraversor.filter(new NodeFilter() { + public FilterResult head(Node node, int depth) { + accum.append("<" + node.nodeName() + ">"); + // OMIT contents of p: + return ("p".equals(node.nodeName())) ? FilterResult.SKIP_CHILDREN : FilterResult.CONTINUE; + } + + public FilterResult tail(Node node, int depth) { + accum.append(""); + return FilterResult.CONTINUE; + } + }, doc.select("div")); + assertEquals("

<#text>
", accum.toString()); + } + + @Test + public void filterSkipEntirely() { + Document doc = Jsoup.parse("

Hello

There
"); + final StringBuilder accum = new StringBuilder(); + NodeTraversor.filter(new NodeFilter() { + public FilterResult head(Node node, int depth) { + // OMIT p: + if ("p".equals(node.nodeName())) + return FilterResult.SKIP_ENTIRELY; + accum.append("<" + node.nodeName() + ">"); + return FilterResult.CONTINUE; + } + + public FilterResult tail(Node node, int depth) { + accum.append(""); + return FilterResult.CONTINUE; + } + }, doc.select("div")); + assertEquals("
<#text>
", accum.toString()); + } + + @Test + public void filterRemove() { + Document doc = Jsoup.parse("

Hello

There be bold
"); + NodeTraversor.filter(new NodeFilter() { + public FilterResult head(Node node, int depth) { + // Delete "p" in head: + return ("p".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE; + } + + public FilterResult tail(Node node, int depth) { + // Delete "b" in tail: + return ("b".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE; + } + }, doc.select("div")); + assertEquals("
\n
\n There be \n
", doc.select("body").html()); + } + + @Test + public void filterStop() { + Document doc = Jsoup.parse("

Hello

There
"); + final StringBuilder accum = new StringBuilder(); + NodeTraversor.filter(new NodeFilter() { + public FilterResult head(Node node, int depth) { + accum.append("<" + node.nodeName() + ">"); + return FilterResult.CONTINUE; + } + + public FilterResult tail(Node node, int depth) { + accum.append(""); + // Stop after p. + return ("p".equals(node.nodeName())) ? FilterResult.STOP : FilterResult.CONTINUE; + } + }, doc.select("div")); + assertEquals("

<#text>

", accum.toString()); + } +} From 57dbebc884455205ea221f942723a496d399f86a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 15:52:26 -0700 Subject: [PATCH 162/774] Deprecation note --- src/main/java/org/jsoup/select/NodeTraversor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index a732245f98..9e0fe26734 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -17,6 +17,7 @@ public class NodeTraversor { /** * Create a new traversor. * @param visitor a class implementing the {@link NodeVisitor} interface, to be called when visiting each node. + * @deprecated Just use the static {@link NodeTraversor#filter(NodeFilter, Node)} method. */ public NodeTraversor(NodeVisitor visitor) { this.visitor = visitor; @@ -25,6 +26,7 @@ public NodeTraversor(NodeVisitor visitor) { /** * Start a depth-first traverse of the root and all of its descendants. * @param root the root node point to traverse. + * @deprecated Just use the static {@link NodeTraversor#filter(NodeFilter, Node)} method. */ public void traverse(Node root) { traverse(visitor, root); From f75a462e020e98c2f90354b0d3ae5e6141e0e533 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 16:02:29 -0700 Subject: [PATCH 163/774] Changelog for #849 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index afe3b344b0..914554e3c4 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,9 @@ jsoup changelog * Added missing support for template tags in tables + * Improved Node traversal, including less object creation, and partial and filtering traversor support. + + * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. From a97672ad240f679f0071e046a119edb6d7dcbb1c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 17:36:34 -0700 Subject: [PATCH 164/774] Added Element.selectFirst() --- CHANGES | 2 + src/main/java/org/jsoup/nodes/Element.java | 11 +++++ src/main/java/org/jsoup/select/Collector.java | 38 ++++++++++++++ .../org/jsoup/select/CombiningEvaluator.java | 2 +- src/main/java/org/jsoup/select/Selector.java | 49 ++++++++----------- .../java/org/jsoup/select/SelectorTest.java | 18 +++++++ 6 files changed, 90 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index 914554e3c4..c60ffb7aa4 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,8 @@ jsoup changelog LinkedHashSet. + * Added support for Element.selectFirst(query), to efficiently find the first matching element. + * Added Element.appendTo(parent) to simplify slinging elements about. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index cb22f74951..8ebc49569b 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -363,6 +363,17 @@ public Elements select(String cssQuery) { return Selector.select(cssQuery, this); } + /** + * Find the first Element that matches the {@link Selector} CSS query, with this element as the starting context. + *

This is effectively the same as calling {@code element.select(query).first()}, but is more efficient as query + * execution stops on the first hit.

+ * @param cssQuery cssQuery a {@link Selector} CSS-like query + * @return the first matching element, or {@code null} if there is no match. + */ + public Element selectFirst(String cssQuery) { + return Selector.selectFirst(cssQuery, this); + } + /** * Check if this element matches the given {@link Selector} CSS query. * @param cssQuery a {@link Selector} CSS query diff --git a/src/main/java/org/jsoup/select/Collector.java b/src/main/java/org/jsoup/select/Collector.java index a28d3b641a..de34eddfad 100644 --- a/src/main/java/org/jsoup/select/Collector.java +++ b/src/main/java/org/jsoup/select/Collector.java @@ -3,6 +3,9 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import static org.jsoup.select.NodeFilter.FilterResult.CONTINUE; +import static org.jsoup.select.NodeFilter.FilterResult.STOP; + /** * Collects a list of elements that match the supplied criteria. * @@ -48,4 +51,39 @@ public void tail(Node node, int depth) { // void } } + + public static Element findFirst(Evaluator eval, Element root) { + FirstFinder finder = new FirstFinder(root, eval); + NodeTraversor.filter(finder, root); + return finder.match; + } + + private static class FirstFinder implements NodeFilter { + private final Element root; + private Element match = null; + private final Evaluator eval; + + FirstFinder(Element root, Evaluator eval) { + this.root = root; + this.eval = eval; + } + + @Override + public FilterResult head(Node node, int depth) { + if (node instanceof Element) { + Element el = (Element) node; + if (eval.matches(root, el)) { + match = el; + return STOP; + } + } + return CONTINUE; + } + + @Override + public FilterResult tail(Node node, int depth) { + return CONTINUE; + } + } + } diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index 9cb163a4a1..94e97c59c1 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -100,7 +100,7 @@ public boolean matches(Element root, Element node) { @Override public String toString() { - return String.format(":or%s", evaluators); + return StringUtil.join(evaluators, ", "); } } } diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index d0130c09f4..8983ed4a5c 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -9,7 +9,7 @@ /** * CSS-like element selector, that finds elements matching a query. - * + * *

Selector syntax

*

* A selector is a chain of simple selectors, separated by combinators. Selectors are case insensitive (including against @@ -69,32 +69,13 @@ * :only-of-type an element that has a parent element and whose parent element has no other element children with the same expanded element name * :emptyelements that have no children at all * - * + * * @author Jonathan Hedley, jonathan@hedley.net * @see Element#select(String) */ public class Selector { - private final Evaluator evaluator; - private final Element root; - - private Selector(String query, Element root) { - Validate.notNull(query); - query = query.trim(); - Validate.notEmpty(query); - Validate.notNull(root); - - this.evaluator = QueryParser.parse(query); - - this.root = root; - } - - private Selector(Evaluator evaluator, Element root) { - Validate.notNull(evaluator); - Validate.notNull(root); - - this.evaluator = evaluator; - this.root = root; - } + // not instantiable + private Selector() {} /** * Find elements matching selector. @@ -105,7 +86,8 @@ private Selector(Evaluator evaluator, Element root) { * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. */ public static Elements select(String query, Element root) { - return new Selector(query, root).select(); + Validate.notEmpty(query); + return select(QueryParser.parse(query), root); } /** @@ -116,7 +98,9 @@ public static Elements select(String query, Element root) { * @return matching elements, empty if none */ public static Elements select(Evaluator evaluator, Element root) { - return new Selector(evaluator, root).select(); + Validate.notNull(evaluator); + Validate.notNull(root); + return Collector.collect(evaluator, root); } /** @@ -146,10 +130,6 @@ public static Elements select(String query, Iterable roots) { return new Elements(elements); } - private Elements select() { - return Collector.collect(evaluator, root); - } - // exclude set. package open so that Elements can implement .not() selector. static Elements filterOut(Collection elements, Collection outs) { Elements output = new Elements(); @@ -167,6 +147,17 @@ static Elements filterOut(Collection elements, Collection outs return output; } + /** + * Find the first element that matches the query. + * @param cssQuery CSS selector + * @param root root element to descend into + * @return the matching element, or null if none. + */ + public static Element selectFirst(String cssQuery, Element root) { + Validate.notEmpty(cssQuery); + return Collector.findFirst(QueryParser.parse(cssQuery), root); + } + public static class SelectorParseException extends IllegalStateException { public SelectorParseException(String msg, Object... params) { super(String.format(msg, params)); diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 6126406b62..f0e36d43d3 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -748,4 +748,22 @@ public void selectClassWithSpace() { assertEquals(1, els.size()); assertEquals("One'One", els.text()); } + + @Test public void selectFirst() { + String html = "

One

Two

Three"; + Document doc = Jsoup.parse(html); + assertEquals("One", doc.selectFirst("p").text()); + } + + @Test public void selectFirstWithAnd() { + String html = "

One

Two

Three"; + Document doc = Jsoup.parse(html); + assertEquals("Two", doc.selectFirst("p.foo").text()); + } + + @Test public void selectFirstWithOr() { + String html = "

One

Two

Three

Four"; + Document doc = Jsoup.parse(html); + assertEquals("One", doc.selectFirst("p, div").text()); + } } From ea00e065f6245a94f87becf7d6ea80c46bc21ef1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 21:21:16 -0700 Subject: [PATCH 165/774] Added support for multiple headers --- CHANGES | 2 + src/main/java/org/jsoup/Connection.java | 35 +++++++- .../java/org/jsoup/helper/HttpConnection.java | 86 ++++++++++++++----- .../org/jsoup/helper/HttpConnectionTest.java | 33 +++++++ 4 files changed, 130 insertions(+), 26 deletions(-) diff --git a/CHANGES b/CHANGES index c60ffb7aa4..acda8fe93f 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,8 @@ jsoup changelog * Added Element.appendTo(parent) to simplify slinging elements about. + * Added support for multiple headers with the same name in Jsoup.Connect + * Updated Element.text() and the :contains(text) selector to consider   character as spaces. * Improved performance of Node.addChildren (was quadratic) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 8040886d2a..5b8a017432 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -9,6 +9,7 @@ import java.net.Proxy; import java.net.URL; import java.util.Collection; +import java.util.List; import java.util.Map; /** @@ -365,7 +366,7 @@ interface Base { T method(Method method); /** - * Get the value of a header. This is a simplified header model, where a header may only have one value. + * Get the value of a header. If there is more than one header with the same name, returns the first header. *

* Header names are case insensitive. *

@@ -377,13 +378,30 @@ interface Base { String header(String name); /** - * Set a header. This method will overwrite any existing header with the same case insensitive name. + * Get the values of a header. + * @param name header name, case insensitive. + * @return a list of values for this header, or an empty list if not set. + */ + List headers(String name); + + /** + * Set a header. This method will overwrite any existing header with the same case insensitive name. (If there + * is more than one value for this header, this method will update the first matching header. * @param name Name of header * @param value Value of header * @return this, for chaining + * @see #addHeader(String, String) */ T header(String name, String value); + /** + * Add a header. The header will be added regardless of whether a header with the same name already exists. + * @param name Name of new header + * @param value Value of new header + * @return this, for chaining + */ + T addHeader(String name, String value); + /** * Check if a header is present * @param name name of header (case insensitive) @@ -400,18 +418,27 @@ interface Base { boolean hasHeaderWithValue(String name, String value); /** - * Remove a header by name + * Remove headers by name. If there is more than one header with this name, they will all be removed. * @param name name of header to remove (case insensitive) * @return this, for chaining */ T removeHeader(String name); /** - * Retrieve all of the request/response headers as a map + * Retrieve all of the request/response header names and corresponding values as a map. For headers with multiple + * values, only the first header is returned. * @return headers + * @see #multiHeaders() + */ Map headers(); + /** + * Retreive all of the headers, keyed by the header name, and with a list of values per header. + * @return a list of multiple values per header. + */ + Map> multiHeaders(); + /** * Get a cookie value by name from this request/response. *

diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 3040039828..a4682a5e17 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -39,6 +39,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -318,7 +319,7 @@ public Connection postDataCharset(String charset) { private static abstract class Base implements Connection.Base { URL url; Method method; - Map headers; + Map> headers; Map cookies; private Base() { @@ -348,7 +349,11 @@ public T method(Method method) { public String header(String name) { Validate.notNull(name, "Header name must not be null"); - String val = getHeaderCaseInsensitive(name); + List vals = getHeadersCaseInsensitive(name); + String val = null; + if (vals.size() > 0) + val = vals.get(0); + if (val != null) { // headers should be ISO8859 - but values are often actually UTF-8. Test if it looks like UTF8 and convert if so val = fixHeaderEncoding(val); @@ -356,6 +361,27 @@ public String header(String name) { return val; } + @Override + public T addHeader(String name, String value) { + Validate.notEmpty(name); + Validate.notNull(value); + + List values = headers(name); + if (values.isEmpty()) { + values = new ArrayList<>(); + headers.put(name, values); + } + values.add(value); + + return (T) this; + } + + @Override + public List headers(String name) { + Validate.notEmpty(name); + return getHeadersCaseInsensitive(name); + } + private static String fixHeaderEncoding(String val) { try { byte[] bytes = val.getBytes("ISO-8859-1"); @@ -409,51 +435,67 @@ public T header(String name, String value) { Validate.notEmpty(name, "Header name must not be empty"); Validate.notNull(value, "Header value must not be null"); removeHeader(name); // ensures we don't get an "accept-encoding" and a "Accept-Encoding" - headers.put(name, value); + addHeader(name, value); return (T) this; } public boolean hasHeader(String name) { Validate.notEmpty(name, "Header name must not be empty"); - return getHeaderCaseInsensitive(name) != null; + return getHeadersCaseInsensitive(name).size() != 0; } /** * Test if the request has a header with this value (case insensitive). */ public boolean hasHeaderWithValue(String name, String value) { - return hasHeader(name) && header(name).equalsIgnoreCase(value); + Validate.notEmpty(name); + Validate.notEmpty(value); + List values = headers(name); + for (String candidate : values) { + if (value.equalsIgnoreCase(candidate)) + return true; + } + return false; } public T removeHeader(String name) { Validate.notEmpty(name, "Header name must not be empty"); - Map.Entry entry = scanHeaders(name); // remove is case insensitive too + Map.Entry> entry = scanHeaders(name); // remove is case insensitive too if (entry != null) headers.remove(entry.getKey()); // ensures correct case return (T) this; } public Map headers() { + LinkedHashMap map = new LinkedHashMap<>(headers.size()); + for (Map.Entry> entry : headers.entrySet()) { + String header = entry.getKey(); + List values = entry.getValue(); + if (values.size() > 0) + map.put(header, values.get(0)); + } + return map; + } + + @Override + public Map> multiHeaders() { return headers; } - private String getHeaderCaseInsensitive(String name) { - Validate.notNull(name, "Header name must not be null"); - // quick evals for common case of title case, lower case, then scan for mixed - String value = headers.get(name); - if (value == null) - value = headers.get(lowerCase(name)); - if (value == null) { - Map.Entry entry = scanHeaders(name); - if (entry != null) - value = entry.getValue(); + private List getHeadersCaseInsensitive(String name) { + Validate.notNull(name); + + for (Map.Entry> entry : headers.entrySet()) { + if (name.equalsIgnoreCase(entry.getKey())) + return entry.getValue(); } - return value; + + return Collections.emptyList(); } - private Map.Entry scanHeaders(String name) { + private Map.Entry> scanHeaders(String name) { String lc = lowerCase(name); - for (Map.Entry entry : headers.entrySet()) { + for (Map.Entry> entry : headers.entrySet()) { if (lowerCase(entry.getKey()).equals(lc)) return entry; } @@ -502,14 +544,14 @@ public static class Request extends HttpConnection.Base impl private boolean validateTSLCertificates = true; private String postDataCharset = DataUtil.defaultCharset; - private Request() { + Request() { timeoutMilliseconds = 30000; // 30 seconds maxBodySizeBytes = 1024 * 1024; // 1MB followRedirects = true; data = new ArrayList<>(); method = Method.GET; - headers.put("Accept-Encoding", "gzip"); - headers.put(USER_AGENT, DEFAULT_UA); + addHeader("Accept-Encoding", "gzip"); + addHeader(USER_AGENT, DEFAULT_UA); parser = Parser.htmlParser(); } diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index ef9042f6ed..494657999b 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -92,6 +92,39 @@ public class HttpConnectionTest { assertEquals("no-cache, no-store", res.header("Cache-Control")); } + @Test public void multipleHeaders() { + Connection.Request req = new HttpConnection.Request(); + req.addHeader("Accept", "Something"); + req.addHeader("Accept", "Everything"); + req.addHeader("Foo", "Bar"); + + assertTrue(req.hasHeader("Accept")); + assertTrue(req.hasHeader("ACCEpt")); + assertEquals("Something", req.header("accept")); + assertTrue(req.hasHeader("fOO")); + assertEquals("Bar", req.header("foo")); + + List accept = req.headers("accept"); + assertEquals(2, accept.size()); + assertEquals("Something", accept.get(0)); + assertEquals("Everything", accept.get(1)); + + Map> headers = req.multiHeaders(); + assertEquals(accept, headers.get("Accept")); + assertEquals("Bar", headers.get("Foo").get(0)); + + assertTrue(req.hasHeader("Accept")); + assertTrue(req.hasHeaderWithValue("accept", "Something")); + assertTrue(req.hasHeaderWithValue("accept", "Everything")); + assertFalse(req.hasHeaderWithValue("accept", "Something for nothing")); + + req.removeHeader("accept"); + headers = req.multiHeaders(); + assertEquals("Bar", headers.get("Foo").get(0)); + assertFalse(req.hasHeader("Accept")); + assertTrue(headers.get("Accept") == null); + } + @Test public void ignoresEmptySetCookies() { // prep http response header map Map> headers = new HashMap>(); From f7e2868fa34b96b5c69d583ac36399dcb33fcc41 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 22:32:06 -0700 Subject: [PATCH 166/774] Note that headers() map is a view. --- src/main/java/org/jsoup/Connection.java | 2 ++ src/test/java/org/jsoup/helper/HttpConnectionTest.java | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 5b8a017432..dcabcf278d 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -427,6 +427,8 @@ interface Base { /** * Retrieve all of the request/response header names and corresponding values as a map. For headers with multiple * values, only the first header is returned. + *

Note that this is a view of the headers only, and changes made to this map will not be reflected in the + * request/response object.

* @return headers * @see #multiHeaders() diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 494657999b..f01a047a63 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -43,10 +43,9 @@ public class HttpConnectionTest { @Test @MultiLocaleTest public void caseInsensitiveHeaders() { Connection.Response res = new HttpConnection.Response(); - Map headers = res.headers(); - headers.put("Accept-Encoding", "gzip"); - headers.put("content-type", "text/html"); - headers.put("refErrer", "http://example.com"); + res.header("Accept-Encoding", "gzip"); + res.header("content-type", "text/html"); + res.header("refErrer", "http://example.com"); assertTrue(res.hasHeader("Accept-Encoding")); assertTrue(res.hasHeader("accept-encoding")); From 35b592536592219f1b7731641726f0c7585039a5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 8 Oct 2017 23:01:50 -0700 Subject: [PATCH 167/774] Multiple headers should be comma joined --- src/main/java/org/jsoup/Connection.java | 3 +- .../java/org/jsoup/helper/HttpConnection.java | 40 +++++++------------ .../org/jsoup/helper/HttpConnectionTest.java | 2 +- .../org/jsoup/integration/UrlConnectTest.java | 11 ++++- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index dcabcf278d..6a6a0ea4a3 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -366,7 +366,8 @@ interface Base { T method(Method method); /** - * Get the value of a header. If there is more than one header with the same name, returns the first header. + * Get the value of a header. If there is more than one header value with the same name, the headers are returned + * comma seperated, per rfc2616-sec4. *

* Header names are case insensitive. *

diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index a4682a5e17..285364a370 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -350,28 +350,25 @@ public T method(Method method) { public String header(String name) { Validate.notNull(name, "Header name must not be null"); List vals = getHeadersCaseInsensitive(name); - String val = null; - if (vals.size() > 0) - val = vals.get(0); - - if (val != null) { - // headers should be ISO8859 - but values are often actually UTF-8. Test if it looks like UTF8 and convert if so - val = fixHeaderEncoding(val); + if (vals.size() > 0) { + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + return StringUtil.join(vals, ", "); } - return val; + + return null; } @Override public T addHeader(String name, String value) { Validate.notEmpty(name); - Validate.notNull(value); + value = value == null ? "" : value; List values = headers(name); if (values.isEmpty()) { values = new ArrayList<>(); headers.put(name, values); } - values.add(value); + values.add(fixHeaderEncoding(value)); return (T) this; } @@ -433,7 +430,6 @@ private static boolean looksLikeUtf8(byte[] input) { public T header(String name, String value) { Validate.notEmpty(name, "Header name must not be empty"); - Validate.notNull(value, "Header value must not be null"); removeHeader(name); // ensures we don't get an "accept-encoding" and a "Accept-Encoding" addHeader(name, value); return (T) this; @@ -896,8 +892,10 @@ private static HttpURLConnection createConnection(Connection.Request req) throws conn.setDoOutput(true); if (req.cookies().size() > 0) conn.addRequestProperty("Cookie", getRequestCookieString(req)); - for (Map.Entry header : req.headers().entrySet()) { - conn.addRequestProperty(header.getKey(), header.getValue()); + for (Map.Entry> header : req.multiHeaders().entrySet()) { + for (String value : header.getValue()) { + conn.addRequestProperty(header.getKey(), value); + } } return conn; } @@ -1019,19 +1017,9 @@ void processResponseHeaders(Map> resHeaders) { if (cookieName.length() > 0) cookie(cookieName, cookieVal); } - } else { // combine same header names with comma: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - if (values.size() == 1) - header(name, values.get(0)); - else if (values.size() > 1) { - StringBuilder accum = StringUtil.stringBuilder(); - for (int i = 0; i < values.size(); i++) { - final String val = values.get(i); - if (i != 0) - accum.append(", "); - accum.append(val); - } - header(name, accum.toString()); - } + } + for (String value : values) { + addHeader(name, value); } } } diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index f01a047a63..94a5398b5f 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -99,7 +99,7 @@ public class HttpConnectionTest { assertTrue(req.hasHeader("Accept")); assertTrue(req.hasHeader("ACCEpt")); - assertEquals("Something", req.header("accept")); + assertEquals("Something, Everything", req.header("accept")); assertTrue(req.hasHeader("fOO")); assertEquals("Bar", req.header("foo")); diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 4a9dbf8f14..777e86c2c9 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -22,6 +22,7 @@ import java.net.MalformedURLException; import java.net.Proxy; import java.net.URL; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -614,8 +615,14 @@ public void combinesSameHeadersWithComma() throws IOException { Connection con = Jsoup.connect(url); con.get(); - assertEquals("text/html", con.response().header("Content-Type")); - assertEquals("no-cache, no-store", con.response().header("Cache-Control")); + Connection.Response res = con.response(); + assertEquals("text/html", res.header("Content-Type")); + assertEquals("no-cache, no-store", res.header("Cache-Control")); + + List header = res.headers("Cache-Control"); + assertEquals(2, header.size()); + assertEquals("no-cache", header.get(0)); + assertEquals("no-store", header.get(1)); } @Test From 22a17d24279450e7e214b57beb4c0bda5f3698e5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 9 Oct 2017 12:35:15 -0700 Subject: [PATCH 168/774] Small code cleanup --- src/main/java/org/jsoup/helper/HttpConnection.java | 2 +- src/main/java/org/jsoup/nodes/Attributes.java | 2 +- src/main/java/org/jsoup/nodes/DataNode.java | 4 ++-- src/main/java/org/jsoup/nodes/Node.java | 2 +- src/main/java/org/jsoup/nodes/TextNode.java | 4 ++-- src/main/java/org/jsoup/parser/TokenQueue.java | 2 +- src/main/java/org/jsoup/parser/Tokeniser.java | 2 +- src/main/java/org/jsoup/parser/XmlTreeBuilder.java | 2 +- src/main/java/org/jsoup/select/NodeFilter.java | 2 +- .../java/org/jsoup/helper/HttpConnectionTest.java | 12 ++++++------ src/test/java/org/jsoup/nodes/BuildEntities.java | 13 ++++++------- src/test/java/org/jsoup/nodes/DocumentTest.java | 2 +- src/test/java/org/jsoup/nodes/ElementTest.java | 8 ++++---- 13 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 285364a370..2d6d116bc7 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -922,7 +922,7 @@ public boolean verify(String urlHostName, SSLSession session) { * please not that this method will only perform action if sslSocketFactory is not yet * instantiated. * - * @throws IOException + * @throws IOException on SSL init errors */ private static synchronized void initUnSecureTSL() throws IOException { if (sslSocketFactory == null) { diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index a52bcdad17..188bbe37f0 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -85,7 +85,7 @@ private int indexOfKeyIgnoreCase(String key) { } // we track boolean attributes as null in values - they're just keys. so returns empty for consumers - static final String checkNotNull(String val) { + static String checkNotNull(String val) { return val == null ? EmptyString : val; } diff --git a/src/main/java/org/jsoup/nodes/DataNode.java b/src/main/java/org/jsoup/nodes/DataNode.java index b71cd4aefe..7981a7ea67 100644 --- a/src/main/java/org/jsoup/nodes/DataNode.java +++ b/src/main/java/org/jsoup/nodes/DataNode.java @@ -20,7 +20,7 @@ public DataNode(String data) { Create a new DataNode. @param data data contents @param baseUri Unused, Leaf Nodes do not hold base URis - @deprecated + @deprecated use {@link #DataNode(String)} instead */ public DataNode(String data, String baseUri) { this(data); @@ -67,6 +67,6 @@ public String toString() { */ public static DataNode createFromEncoded(String encodedData, String baseUri) { String data = Entities.unescape(encodedData); - return new DataNode(data, baseUri); + return new DataNode(data); } } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index cdba9061e7..15d8f2bda6 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -135,7 +135,7 @@ public Node clearAttributes() { /** * Set the baseUri for just this node (not its descendants), if this Node tracks base URIs. - * @param baseUri + * @param baseUri new URI */ protected abstract void doSetBaseUri(String baseUri); diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 1e7f5ae60e..2037365ec4 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -27,7 +27,7 @@ Create a new TextNode representing the supplied (unencoded) text). @param text raw text @param baseUri base uri - ignored for this node type @see #createFromEncoded(String, String) - @deprecated use {@link TextNode(String)} + @deprecated use {@link TextNode#TextNode(String)} */ public TextNode(String text, String baseUri) { this(text); @@ -86,7 +86,7 @@ public TextNode splitText(int offset) { String head = text.substring(0, offset); String tail = text.substring(offset); text(head); - TextNode tailNode = new TextNode(tail, this.baseUri()); + TextNode tailNode = new TextNode(tail); if (parent() != null) parent().addChildren(siblingIndex()+1, tailNode); diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 96e30e858c..5fa6f11eff 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -293,7 +293,7 @@ else if (c.equals(close)) } /** - * Unescaped a \ escaped string. + * Unescape a \ escaped string. * @param in backslash escaped string * @return unescaped string */ diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index b031c9b317..21a1fac026 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -255,7 +255,7 @@ boolean currentNodeInHtmlNS() { /** * Utility method to consume reader and unescape entities found within. - * @param inAttribute + * @param inAttribute if the text to be unescaped is in an attribute * @return unescaped string from reader */ String unescapeEntities(boolean inAttribute) { diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 631a74a152..b136804118 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -111,7 +111,7 @@ void insert(Token.Doctype d) { * If the stack contains an element with this tag's name, pop up the stack to remove the first occurrence. If not * found, skips. * - * @param endTag + * @param endTag tag to close */ private void popStackToClose(Token.EndTag endTag) { String elName = endTag.name(); diff --git a/src/main/java/org/jsoup/select/NodeFilter.java b/src/main/java/org/jsoup/select/NodeFilter.java index 821f98d2aa..9f80816485 100644 --- a/src/main/java/org/jsoup/select/NodeFilter.java +++ b/src/main/java/org/jsoup/select/NodeFilter.java @@ -27,7 +27,7 @@ public interface NodeFilter { /** * Filter decision. */ - public enum FilterResult { + enum FilterResult { /** Continue processing the tree */ CONTINUE, /** Skip the child nodes, but do call {@link NodeFilter#tail(Node, int)} next. */ diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 94a5398b5f..94eb4dfc53 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -70,7 +70,7 @@ public class HttpConnectionTest { @Test public void headers() { Connection con = HttpConnection.connect("http://example.com"); - Map headers = new HashMap(); + Map headers = new HashMap<>(); headers.put("content-type", "text/html"); headers.put("Connection", "keep-alive"); headers.put("Host", "http://example.com"); @@ -81,8 +81,8 @@ public class HttpConnectionTest { } @Test public void sameHeadersCombineWithComma() { - Map> headers = new HashMap>(); - List values = new ArrayList(); + Map> headers = new HashMap<>(); + List values = new ArrayList<>(); values.add("no-cache"); values.add("no-store"); headers.put("Cache-Control", values); @@ -126,7 +126,7 @@ public class HttpConnectionTest { @Test public void ignoresEmptySetCookies() { // prep http response header map - Map> headers = new HashMap>(); + Map> headers = new HashMap<>(); headers.put("Set-Cookie", Collections.emptyList()); HttpConnection.Response res = new HttpConnection.Response(); res.processResponseHeaders(headers); @@ -135,8 +135,8 @@ public class HttpConnectionTest { @Test public void ignoresEmptyCookieNameAndVals() { // prep http response header map - Map> headers = new HashMap>(); - List cookieStrings = new ArrayList(); + Map> headers = new HashMap<>(); + List cookieStrings = new ArrayList<>(); cookieStrings.add(null); cookieStrings.add(""); cookieStrings.add("one"); diff --git a/src/test/java/org/jsoup/nodes/BuildEntities.java b/src/test/java/org/jsoup/nodes/BuildEntities.java index 694a1a90b2..f20c0209e7 100644 --- a/src/test/java/org/jsoup/nodes/BuildEntities.java +++ b/src/test/java/org/jsoup/nodes/BuildEntities.java @@ -5,7 +5,6 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.integration.UrlConnectTest; -import org.jsoup.nodes.Entities; import java.io.File; import java.io.FileWriter; @@ -37,8 +36,8 @@ public static void main(String[] args) throws IOException { // build name sorted base and full character lists: - ArrayList base = new ArrayList(); - ArrayList full = new ArrayList(); + ArrayList base = new ArrayList<>(); + ArrayList full = new ArrayList<>(); for (Map.Entry entry : input.entrySet()) { String name = entry.getKey().substring(1); // name is like ´ or ´ , trim & @@ -55,13 +54,13 @@ public static void main(String[] args) throws IOException { Collections.sort(full, byName); // now determine code point order - ArrayList baseByCode = new ArrayList(base); - ArrayList fullByCode = new ArrayList(full); + ArrayList baseByCode = new ArrayList<>(base); + ArrayList fullByCode = new ArrayList<>(full); Collections.sort(baseByCode, byCode); Collections.sort(fullByCode, byCode); - // and update their codepoint index. Don't - ArrayList[] codelists = new ArrayList[]{baseByCode, fullByCode}; + // and update their codepoint index. + @SuppressWarnings("unchecked") ArrayList[] codelists = new ArrayList[]{baseByCode, fullByCode}; for (ArrayList codelist : codelists) { for (int i = 0; i < codelist.size(); i++) { codelist.get(i).codeIndex = i; diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 6c3b71ad1d..f96dad1aa9 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -404,7 +404,7 @@ private Document createXmlDocument(String version, String charset, boolean addDe doc.appendElement("root").text("node"); doc.outputSettings().syntax(Syntax.xml); - if( addDecl == true ) { + if(addDecl) { XmlDeclaration decl = new XmlDeclaration("xml", false); decl.attr("version", version); decl.attr("encoding", charset); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index bb49d25f41..9fc7d2e38a 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -780,7 +780,7 @@ public void insertChildrenAtPosition() { assertEquals(4, div2.childNodeSize()); assertEquals(3, p1s.get(1).siblingIndex()); // should be last - List els = new ArrayList(); + List els = new ArrayList<>(); Element el1 = new Element(Tag.valueOf("span"), "").text("Span1"); Element el2 = new Element(Tag.valueOf("span"), "").text("Span2"); TextNode tn1 = new TextNode("Text4"); @@ -847,7 +847,7 @@ public void testClassNames() { assertEquals("c1 c2", div.className()); // Update the class names to a fresh set - final Set newSet = new LinkedHashSet(3); + final Set newSet = new LinkedHashSet<>(3); newSet.addAll(set1); newSet.add("c3"); @@ -937,7 +937,7 @@ public void appendMustCorrectlyMoveChildrenInsideOneParentElement() { div3.text("Check"); final Element div4 = body.appendElement("div4"); - ArrayList toMove = new ArrayList(); + ArrayList toMove = new ArrayList<>(); toMove.add(div3); toMove.add(div4); @@ -951,7 +951,7 @@ public void appendMustCorrectlyMoveChildrenInsideOneParentElement() { public void testHashcodeIsStableWithContentChanges() { Element root = new Element(Tag.valueOf("root"), ""); - HashSet set = new HashSet(); + HashSet set = new HashSet<>(); // Add root node: set.add(root); From c324fabe86c0a59eb635610dc85d8d7a7be25c74 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 9 Oct 2017 14:55:26 -0700 Subject: [PATCH 169/774] Notifications to cloned elements were incorrectly delivered to the original element. Fixes #951 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Element.java | 14 +++++----- .../java/org/jsoup/nodes/ElementTest.java | 26 ++++++++++++++++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index acda8fe93f..b31fb375e2 100644 --- a/CHANGES +++ b/CHANGES @@ -108,6 +108,9 @@ jsoup changelog * Bugfix: in certain locales (Turkey specifically), lowercasing and case insensitivity could fail for specific items. + * Bugfix: after an element was cloned, changes to its child list where not notifying the element correctly. + + *** Release 1.10.2 [2017-Jan-02] * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading the HTML entity files. About 1.72x faster in this area. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 8ebc49569b..85d335af9c 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -86,7 +86,7 @@ public Element(Tag tag, String baseUri) { protected List ensureChildNodes() { if (childNodes == EMPTY_NODES) { - childNodes = new NodeList(4); + childNodes = new NodeList(this, 4); } return childNodes; } @@ -1399,20 +1399,22 @@ protected Element doClone(Node parent) { Element clone = (Element) super.doClone(parent); clone.attributes = attributes != null ? attributes.clone() : null; clone.baseUri = baseUri; - clone.childNodes = new NodeList(childNodes.size()); - + clone.childNodes = new NodeList(clone, childNodes.size()); clone.childNodes.addAll(childNodes); return clone; } - private final class NodeList extends ChangeNotifyingArrayList { - NodeList(int initialCapacity) { + private static final class NodeList extends ChangeNotifyingArrayList { + private final Element owner; + + NodeList(Element owner, int initialCapacity) { super(initialCapacity); + this.owner = owner; } public void onContentsChanged() { - nodelistChanged(); + owner.nodelistChanged(); } } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 9fc7d2e38a..d3d1163ac4 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1209,6 +1209,30 @@ public void whiteSpaceClassElement(){ attribs.put("class", "abc "); boolean hasClass = el.hasClass("ab"); assertFalse(hasClass); - } + + @Test + public void testNextElementSiblingAfterClone() { + // via https://github.com/jhy/jsoup/issues/951 + String html = "
Initial element
"; + String expectedText = "New element"; + String cloneExpect = "New element in clone"; + + Document original = Jsoup.parse(html); + Document clone = original.clone(); + + Element originalElement = original.body().child(0); + originalElement.after("
" + expectedText + "
"); + Element originalNextElementSibling = originalElement.nextElementSibling(); + Element originalNextSibling = (Element) originalElement.nextSibling(); + assertEquals(expectedText, originalNextElementSibling.text()); + assertEquals(expectedText, originalNextSibling.text()); + + Element cloneElement = clone.body().child(0); + cloneElement.after("
" + cloneExpect + "
"); + Element cloneNextElementSibling = cloneElement.nextElementSibling(); + Element cloneNextSibling = (Element) cloneElement.nextSibling(); + assertEquals(cloneExpect, cloneNextElementSibling.text()); + assertEquals(cloneExpect, cloneNextSibling.text()); + } } From a129f801497f6dc90048fa5cd52672274425cc18 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Oct 2017 11:26:15 -0700 Subject: [PATCH 170/774] Simplified and corrected ConstrainableInputStream impl --- src/main/java/org/jsoup/helper/DataUtil.java | 27 +------ .../java/org/jsoup/helper/HttpConnection.java | 4 +- .../internal/ConstrainableInputStream.java | 66 ++++++++++++++-- .../org/jsoup/integration/UrlConnectTest.java | 2 +- .../ConstrainableInputStreamTest.java | 79 +++++++++++++++++++ 5 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index b243109362..796c693c2c 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -8,7 +8,6 @@ import org.jsoup.select.Elements; import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -93,9 +92,7 @@ static void crossStreams(final InputStream in, final OutputStream out) throws IO static Document parseInputStream(InputStream input, String charsetName, String baseUri, Parser parser) throws IOException { if (input == null) // empty body return new Document(baseUri); - - if (!(input instanceof ConstrainableInputStream)) - input = new ConstrainableInputStream(input, bufferSize, 0); + input = ConstrainableInputStream.wrap(input, bufferSize, 0); Document doc = null; boolean fullyRead = false; @@ -167,26 +164,8 @@ static Document parseInputStream(InputStream input, String charsetName, String b */ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) throws IOException { Validate.isTrue(maxSize >= 0, "maxSize must be 0 (unlimited) or larger"); - final boolean capped = maxSize > 0; - final byte[] buffer = new byte[capped && maxSize < bufferSize ? maxSize : bufferSize]; - final ByteArrayOutputStream outStream = new ByteArrayOutputStream(capped ? maxSize : bufferSize); - - int read; - int remaining = maxSize; - - while (true) { - read = inStream.read(buffer); - if (read == -1) break; - if (capped) { // todo - why not using ConstrainedInputStream? - if (read >= remaining) { - outStream.write(buffer, 0, remaining); - break; - } - remaining -= read; - } - outStream.write(buffer, 0, read); - } - return ByteBuffer.wrap(outStream.toByteArray()); + final ConstrainableInputStream input = ConstrainableInputStream.wrap(inStream, bufferSize, maxSize); + return input.readToByteBuffer(maxSize); } static ByteBuffer readToByteBuffer(InputStream inStream) throws IOException { diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 2d6d116bc7..2da641d078 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -774,7 +774,7 @@ else if (methodHasBody) res.bodyStream = conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream(); if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) res.bodyStream = new GZIPInputStream(res.bodyStream); - res.bodyStream = new ConstrainableInputStream(res.bodyStream, DataUtil.bufferSize, req.maxBodySize()); + res.bodyStream = ConstrainableInputStream.wrap(res.bodyStream, DataUtil.bufferSize, req.maxBodySize()); } else { res.byteData = DataUtil.emptyByteBuffer(); } @@ -864,7 +864,7 @@ public BufferedInputStream bodyStream() { Validate.isTrue(executed, "Request must be executed (with .execute(), .get(), or .post() before getting response body"); Validate.isFalse(inputStreamRead, "Request has already been read"); inputStreamRead = true; - return new ConstrainableInputStream(bodyStream, DataUtil.bufferSize, req.maxBodySize()); + return ConstrainableInputStream.wrap(bodyStream, DataUtil.bufferSize, req.maxBodySize()); } // set up connection defaults, and details from request diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 1b8a65dbc9..5af2b9335b 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -3,33 +3,89 @@ import org.jsoup.helper.Validate; import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; /** * A jsoup internal class (so don't use it as there is no contract API) that enables constraints on an Input Stream, * namely a maximum read size, and the ability to Thread.interrupt() the read. */ public final class ConstrainableInputStream extends BufferedInputStream { + private static final int DefaultSize = 1024 * 32; + private final boolean capped; + private final int maxSize; private int remaining; - public ConstrainableInputStream(InputStream in, int bufferSize, int maxSize) { + private ConstrainableInputStream(InputStream in, int bufferSize, int maxSize) { super(in, bufferSize); Validate.isTrue(maxSize >= 0); + this.maxSize = maxSize; remaining = maxSize; capped = maxSize != 0; } + /** + * If this InputStream is not already a ConstrainableInputStream, let it be one. + * @param in the input stream to (maybe) wrap + * @param bufferSize the buffer size to use when reading + * @param maxSize the maximum size to allow to be read. 0 == infinite. + * @return a constrainable input stream + */ + public static ConstrainableInputStream wrap(InputStream in, int bufferSize, int maxSize) { + return in instanceof ConstrainableInputStream + ? (ConstrainableInputStream) in + : new ConstrainableInputStream(in, bufferSize, maxSize); + } + @Override public int read(byte[] b, int off, int len) throws IOException { - if (Thread.interrupted() || remaining < 0) + if (Thread.interrupted() || capped && remaining <= 0) return -1; + if (capped && len > remaining) + len = remaining; // don't read more than desired, even if available + final int read = super.read(b, off, len); - if (capped) { - remaining -= read; - } + remaining -= read; + return read; } + + /** + * Reads this inputstream to a ByteBuffer. The supplied max may be less than the inputstream's max, to support + * reading just the first bytes. + */ + public ByteBuffer readToByteBuffer(int max) throws IOException { + Validate.isTrue(max >= 0, "maxSize must be 0 (unlimited) or larger"); + final boolean localCapped = max > 0; // still possibly capped in total stream + final int bufferSize = localCapped && max < DefaultSize ? max : DefaultSize; + final byte[] readBuffer = new byte[bufferSize]; + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize); + + int read; + int remaining = max; + + while (true) { + read = read(readBuffer); + if (read == -1) break; + if (localCapped) { // this local byteBuffer cap may be smaller than the overall maxSize (like when reading first bytes) + if (read >= remaining) { + outStream.write(readBuffer, 0, remaining); + break; + } + remaining -= read; + } + outStream.write(readBuffer, 0, read); + } + return ByteBuffer.wrap(outStream.toByteArray()); + } + + @Override + public void reset() throws IOException { + super.reset(); + remaining = maxSize - markpos; + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 777e86c2c9..6113f67d3f 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -415,7 +415,7 @@ public void maxBodySize() throws IOException { int actualDocText = 269541; assertEquals(actualDocText, defaultRes.parse().text().length()); - assertEquals(47200, smallRes.parse().text().length()); + assertEquals(49165, smallRes.parse().text().length()); assertEquals(196577, mediumRes.parse().text().length()); assertEquals(actualDocText, largeRes.parse().text().length()); assertEquals(actualDocText, unlimitedRes.parse().text().length()); diff --git a/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java b/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java new file mode 100644 index 0000000000..fb84d9e1a5 --- /dev/null +++ b/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java @@ -0,0 +1,79 @@ +package org.jsoup.internal; + +import org.jsoup.Jsoup; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@Ignore +public class ConstrainableInputStreamTest { + // todo - move these all to local jetty, don't ignore + + @Test + public void remainingAfterFirstRead() throws IOException { + int bufferSize = 5 * 1024; + int capSize = 100 * 1024; + + String url = "http://direct.infohound.net/tools/large.html"; // 280 K + BufferedInputStream inputStream = Jsoup.connect(url).maxBodySize(capSize) + .execute().bodyStream(); + + assertTrue(inputStream instanceof ConstrainableInputStream); + ConstrainableInputStream stream = (ConstrainableInputStream) inputStream; + + // simulates parse which does a limited read first + stream.mark(bufferSize); + ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); + + byte[] array = firstBytes.array(); + String firstText = new String(array, "UTF-8"); + assertTrue(firstText.startsWith("Large")); + assertEquals(bufferSize, array.length); + + boolean fullyRead = stream.read() == -1; + assertFalse(fullyRead); + + // reset and read again + stream.reset(); + ByteBuffer fullRead = stream.readToByteBuffer(0); + byte[] fullArray = fullRead.array(); + assertEquals(capSize, fullArray.length); + String fullText = new String(fullArray, "UTF-8"); + assertTrue(fullText.startsWith(firstText)); + } + + @Test + public void noLimitAfterFirstRead() throws IOException { + int bufferSize = 5 * 1024; + + String url = "http://direct.infohound.net/tools/large.html"; // 280 K + BufferedInputStream inputStream = Jsoup.connect(url).execute().bodyStream(); + + assertTrue(inputStream instanceof ConstrainableInputStream); + ConstrainableInputStream stream = (ConstrainableInputStream) inputStream; + + // simulates parse which does a limited read first + stream.mark(bufferSize); + ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); + byte[] array = firstBytes.array(); + String firstText = new String(array, "UTF-8"); + assertTrue(firstText.startsWith("<html><head><title>Large")); + assertEquals(bufferSize, array.length); + + // reset and read fully + stream.reset(); + ByteBuffer fullRead = stream.readToByteBuffer(0); + byte[] fullArray = fullRead.array(); + assertEquals(280735, fullArray.length); + String fullText = new String(fullArray, "UTF-8"); + assertTrue(fullText.startsWith(firstText)); + + } +} From d8fb52127f867cdf5171429dac2b812382170dd5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Oct 2017 11:53:49 -0700 Subject: [PATCH 171/774] Close input stream after reading to release resources --- .../java/org/jsoup/helper/HttpConnection.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 2da641d078..75055b39d2 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -819,8 +819,8 @@ public Document parse() throws IOException { Validate.isFalse(inputStreamRead, "Input stream already read and parsed, cannot re-read."); Document doc = DataUtil.parseInputStream(bodyStream, charset, url.toExternalForm(), req.parser()); charset = doc.outputSettings().charset().name(); // update charset from meta-equiv, possibly - // todo - disconnect here? inputStreamRead = true; + safeClose(); return doc; } @@ -832,6 +832,9 @@ private void prepareByteData() { byteData = DataUtil.readToByteBuffer(bodyStream, req.maxBodySize()); } catch (IOException e) { throw new UncheckedIOException(e); + } finally { + inputStreamRead = true; + safeClose(); } } } @@ -900,6 +903,21 @@ private static HttpURLConnection createConnection(Connection.Request req) throws return conn; } + /** + * Call on completion of stream read, to close the body (or error) stream + */ + private void safeClose() { + if (bodyStream != null) { + try { + bodyStream.close(); + } catch (IOException e) { + // no-op + } finally { + bodyStream = null; + } + } + } + /** * Instantiate Hostname Verifier that does nothing. * This is used for connections with disabled SSL certificates validation. From 8ec29562144187ccea0c2ceb3bee08d2e9e720bc Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Oct 2017 14:44:43 -0700 Subject: [PATCH 172/774] First pass at a local test server --- pom.xml | 16 ++++++ .../org/jsoup/integration/ConnectTest.java | 31 ++++++++++ .../org/jsoup/integration/TestServer.java | 57 +++++++++++++++++++ .../integration/servlets/BaseServlet.java | 16 ++++++ .../integration/servlets/HelloServlet.java | 21 +++++++ 5 files changed, 141 insertions(+) create mode 100644 src/test/java/org/jsoup/integration/ConnectTest.java create mode 100644 src/test/java/org/jsoup/integration/TestServer.java create mode 100644 src/test/java/org/jsoup/integration/servlets/BaseServlet.java create mode 100644 src/test/java/org/jsoup/integration/servlets/HelloServlet.java diff --git a/pom.xml b/pom.xml index e4135151a5..62df8ff307 100644 --- a/pom.xml +++ b/pom.xml @@ -222,6 +222,22 @@ <scope>test</scope> </dependency> + <dependency> + <!-- jetty for webserver integration tests --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <version>9.4.7.v20170914</version> + </dependency> + + <dependency> + <!-- jetty for webserver integration tests --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + <version>9.4.7.v20170914</version> + </dependency> + + + </dependencies> <dependencyManagement> diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java new file mode 100644 index 0000000000..5b3263054e --- /dev/null +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -0,0 +1,31 @@ +package org.jsoup.integration; + +import org.jsoup.Jsoup; +import org.jsoup.integration.servlets.HelloServlet; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class ConnectTest { + + @BeforeClass public static void setUp() throws Exception { + TestServer.start(); + } + + @AfterClass public static void tearDown() throws Exception { + TestServer.stop(); + } + + @Test public void canConnectToLocalServer() throws IOException { + String url = HelloServlet.Url; + Document doc = Jsoup.connect(url).get(); + Element p = doc.selectFirst("p"); + assertEquals("Hello, World!", p.text()); + } +} diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java new file mode 100644 index 0000000000..11301a277e --- /dev/null +++ b/src/test/java/org/jsoup/integration/TestServer.java @@ -0,0 +1,57 @@ +package org.jsoup.integration; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletHandler; +import org.jsoup.integration.servlets.BaseServlet; + +import java.util.concurrent.atomic.AtomicInteger; + +public class TestServer { + private static final Server jetty = new Server(0); + private static final ServletHandler handler = new ServletHandler(); + private static AtomicInteger latch = new AtomicInteger(0); + + static { + jetty.setHandler(handler); + start(); + } + + private TestServer() { + } + + static void start() { + synchronized (jetty) { + int count = latch.getAndIncrement(); + if (count == 0) { + try { + jetty.start(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + } + + static void stop() { + synchronized (jetty) { + int count = latch.decrementAndGet(); + if (count == 0) { + try { + jetty.stop(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + } + + public static String map(Class<? extends BaseServlet> servletClass) { + synchronized (jetty) { + String path = "/" + servletClass.getSimpleName(); + handler.addServletWithMapping(servletClass, path); + int port = ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); + return "http://localhost:" + port + path; + } + } +} diff --git a/src/test/java/org/jsoup/integration/servlets/BaseServlet.java b/src/test/java/org/jsoup/integration/servlets/BaseServlet.java new file mode 100644 index 0000000000..91276b2c17 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/BaseServlet.java @@ -0,0 +1,16 @@ +package org.jsoup.integration.servlets; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public abstract class BaseServlet extends HttpServlet { + static final String TextHtml = "text/html"; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + super.doGet(req, res); + } +} diff --git a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java new file mode 100644 index 0000000000..0660d1ad6f --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java @@ -0,0 +1,21 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.integration.TestServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class HelloServlet extends BaseServlet { + public static final String Url = TestServer.map(HelloServlet.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + res.setContentType(TextHtml); + res.setStatus(HttpServletResponse.SC_OK); + + String doc = "<p>Hello, World!"; + res.getWriter().write(doc); + } +} From 3c69747280c04ebb3f1df1863bcfbe5f14150c97 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Oct 2017 14:52:46 -0700 Subject: [PATCH 173/774] Enable Jetty to run on Java7, and clean up stop --- pom.xml | 6 +++--- src/test/java/org/jsoup/integration/TestServer.java | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 62df8ff307..000c05b778 100644 --- a/pom.xml +++ b/pom.xml @@ -223,17 +223,17 @@ </dependency> <dependency> - <!-- jetty for webserver integration tests --> + <!-- jetty for webserver integration tests. 9.2 is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.7.v20170914</version> + <version>9.2.22.v20170606</version> </dependency> <dependency> <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.7.v20170914</version> + <version>9.2.22.v20170606</version> </dependency> diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java index 11301a277e..e8247e19f1 100644 --- a/src/test/java/org/jsoup/integration/TestServer.java +++ b/src/test/java/org/jsoup/integration/TestServer.java @@ -14,7 +14,6 @@ public class TestServer { static { jetty.setHandler(handler); - start(); } private TestServer() { From bd789b079038c1b3f41d05f869c244b54840dbca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Oct 2017 16:56:40 -0700 Subject: [PATCH 174/774] Moved some tests to ConnectTest So that they run locally, and actually do run (vs @ ignored). Need to implement the rest too. --- .../java/org/jsoup/helper/StringUtil.java | 10 ++ src/main/java/org/jsoup/nodes/Entities.java | 35 +++- .../org/jsoup/integration/ConnectTest.java | 149 +++++++++++++++++- .../org/jsoup/integration/TestServer.java | 7 +- .../org/jsoup/integration/UrlConnectTest.java | 127 --------------- .../integration/servlets/BaseServlet.java | 13 +- .../integration/servlets/EchoServlet.java | 90 +++++++++++ 7 files changed, 292 insertions(+), 139 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/servlets/EchoServlet.java diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index 90cc69d935..68126046c0 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -47,6 +47,16 @@ public static String join(Iterator strings, String sep) { return sb.toString(); } + /** + * Join an array of strings by a separator + * @param strings collection of string objects + * @param sep string to place between strings + * @return joined string + */ + public static String join(String[] strings, String sep) { + return join(Arrays.asList(strings), sep); + } + /** * Returns space padding * @param width amount of padding desired diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index f0d37a0a4c..2c9795357e 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -28,6 +28,9 @@ public class Entities { private static final String emptyName = ""; static final int codepointRadix = 36; private static final Charset ASCII = Charset.forName("ascii"); + private static final char[] codeDelims = {',', ';'}; + private static final HashMap<String, String> multipoints = new HashMap<>(); // name -> multiple character references + private static final Document.OutputSettings DefaultOutput = new Document.OutputSettings(); public enum EscapeMode { /** @@ -76,8 +79,6 @@ private int size() { } } - private static final HashMap<String, String> multipoints = new HashMap<>(); // name -> multiple character references - private Entities() { } @@ -144,7 +145,16 @@ public static int codepointsForName(final String name, final int[] codepoints) { return 0; } - static String escape(String string, Document.OutputSettings out) { + /** + * HTML escape an input string. That is, {@code <} is returned as + * {@code <} + * @param string the un-escaped string to escape + * @param out the output settings to use + * @return the escaped string + */ + public static String escape(String string, Document.OutputSettings out) { + if (string == null) + return ""; StringBuilder accum = new StringBuilder(string.length() * 2); try { escape(accum, string, out, false, false, false); @@ -154,6 +164,16 @@ static String escape(String string, Document.OutputSettings out) { return accum.toString(); } + /** + * HTML escape an input string, using the default settings (UTF-8, base entities). That is, {@code <} is returned as + * {@code <} + * @param string the un-escaped string to escape + * @return the escaped string + */ + public static String escape(String string) { + return escape(string, DefaultOutput); + } + // this method is ugly, and does a lot. but other breakups cause rescanning and stringbuilder generations static void escape(Appendable accum, String string, Document.OutputSettings out, boolean inAttribute, boolean normaliseWhite, boolean stripLeadingWhite) throws IOException { @@ -238,7 +258,12 @@ private static void appendEncoded(Appendable accum, EscapeMode escapeMode, int c accum.append("&#x").append(Integer.toHexString(codePoint)).append(';'); } - static String unescape(String string) { + /** + * Un-escape an HTML escaped string. That is, {@code <} is returned as {@code <}. + * @param string the HTML string to un-escape + * @return the unescaped string + */ + public static String unescape(String string) { return unescape(string, false); } @@ -290,8 +315,6 @@ static CoreCharset byName(final String name) { } } - private static final char[] codeDelims = {',', ';'}; - private static void load(EscapeMode e, String file, int size) { e.nameKeys = new String[size]; e.codeVals = new int[size]; diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 5b3263054e..355a17ec63 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -1,6 +1,8 @@ package org.jsoup.integration; +import org.jsoup.Connection; import org.jsoup.Jsoup; +import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.HelloServlet; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -9,23 +11,164 @@ import org.junit.Test; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import static org.jsoup.integration.UrlConnectTest.browserUa; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +/** + * Tests Jsoup.connect against a local server. + */ public class ConnectTest { + private static String echoUrl; - @BeforeClass public static void setUp() throws Exception { + @BeforeClass + public static void setUp() throws Exception { TestServer.start(); + echoUrl = EchoServlet.Url; } - @AfterClass public static void tearDown() throws Exception { + @AfterClass + public static void tearDown() throws Exception { TestServer.stop(); } - @Test public void canConnectToLocalServer() throws IOException { + @Test + public void canConnectToLocalServer() throws IOException { String url = HelloServlet.Url; Document doc = Jsoup.connect(url).get(); Element p = doc.selectFirst("p"); assertEquals("Hello, World!", p.text()); } + + @Test + public void fetchURl() throws IOException { + Document doc = Jsoup.parse(new URL(echoUrl), 10 * 1000); + assertTrue(doc.title().contains("Environment Variables")); + } + + @Test + public void fetchURIWithWihtespace() throws IOException { + Connection con = Jsoup.connect(echoUrl + "#with whitespaces"); + Document doc = con.get(); + assertTrue(doc.title().contains("Environment Variables")); + } + + @Test + public void exceptOnUnsupportedProtocol() { + String url = "file://etc/passwd"; + boolean threw = false; + try { + Document doc = Jsoup.connect(url).get(); + } catch (MalformedURLException e) { + threw = true; + assertEquals("java.net.MalformedURLException: Only http & https protocols supported", e.toString()); + } catch (IOException e) { + } + assertTrue(threw); + } + + private static String ihVal(String key, Document doc) { + return doc.select("th:contains(" + key + ") + td").first().text(); + } + + @Test + public void doesPost() throws IOException { + Document doc = Jsoup.connect(echoUrl) + .data("uname", "Jsoup", "uname", "Jonathan", "百", "度一下") + .cookie("auth", "token") + .post(); + + assertEquals("POST", ihVal("Method", doc)); + assertEquals("gzip", ihVal("Accept-Encoding", doc)); + assertEquals("auth=token", ihVal("Cookie", doc)); + assertEquals("度一下", ihVal("百", doc)); + assertEquals("Jsoup, Jonathan", ihVal("uname", doc)); + assertEquals("application/x-www-form-urlencoded; charset=UTF-8", ihVal("Content-Type", doc)); + } + + @Test + public void sendsRequestBodyJsonWithData() throws IOException { + final String body = "{key:value}"; + Document doc = Jsoup.connect(echoUrl) + .requestBody(body) + .header("Content-Type", "application/json") + .userAgent(browserUa) + .data("foo", "true") + .post(); + assertEquals("POST", ihVal("Method", doc)); + assertEquals("application/json", ihVal("Content-Type", doc)); + assertEquals("foo=true", ihVal("Query String", doc)); + assertEquals(body, ihVal("Post Data", doc)); + } + + @Test + public void sendsRequestBodyJsonWithoutData() throws IOException { + final String body = "{key:value}"; + Document doc = Jsoup.connect(echoUrl) + .requestBody(body) + .header("Content-Type", "application/json") + .userAgent(browserUa) + .post(); + assertEquals("POST", ihVal("Method", doc)); + assertEquals("application/json", ihVal("Content-Type", doc)); + assertEquals(body, ihVal("Post Data", doc)); + } + + @Test + public void sendsRequestBody() throws IOException { + final String body = "{key:value}"; + Document doc = Jsoup.connect(echoUrl) + .requestBody(body) + .header("Content-Type", "text/plain") + .userAgent(browserUa) + .post(); + assertEquals("POST", ihVal("Method", doc)); + assertEquals("text/plain", ihVal("Content-Type", doc)); + assertEquals(body, ihVal("Post Data", doc)); + } + + @Test + public void sendsRequestBodyWithUrlParams() throws IOException { + final String body = "{key:value}"; + Document doc = Jsoup.connect(echoUrl) + .requestBody(body) + .data("uname", "Jsoup", "uname", "Jonathan", "百", "度一下") + .header("Content-Type", "text/plain") // todo - if user sets content-type, we should append postcharset + .userAgent(browserUa) + .post(); + assertEquals("POST", ihVal("Method", doc)); + assertEquals("uname=Jsoup&uname=Jonathan&%E7%99%BE=%E5%BA%A6%E4%B8%80%E4%B8%8B", ihVal("Query String", doc)); + assertEquals(body, ihVal("Post Data", doc)); + } + + @Test + public void doesGet() throws IOException { + Connection con = Jsoup.connect(echoUrl + "?what=the") + .userAgent("Mozilla") + .referrer("http://example.com") + .data("what", "about & me?"); + + Document doc = con.get(); + assertEquals("what=the&what=about+%26+me%3F", ihVal("Query String", doc)); + assertEquals("the, about & me?", ihVal("what", doc)); + assertEquals("Mozilla", ihVal("User-Agent", doc)); + assertEquals("http://example.com", ihVal("Referer", doc)); + } + + @Test + public void doesPut() throws IOException { + Connection.Response res = Jsoup.connect(echoUrl) + .data("uname", "Jsoup", "uname", "Jonathan", "百", "度一下") + .cookie("auth", "token") + .method(Connection.Method.PUT) + .execute(); + + Document doc = res.parse(); + assertEquals("PUT", ihVal("Method", doc)); + assertEquals("gzip", ihVal("Accept-Encoding", doc)); + assertEquals("auth=token", ihVal("Cookie", doc)); + } } diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java index e8247e19f1..bf1a089ef0 100644 --- a/src/test/java/org/jsoup/integration/TestServer.java +++ b/src/test/java/org/jsoup/integration/TestServer.java @@ -19,7 +19,7 @@ public class TestServer { private TestServer() { } - static void start() { + public static void start() { synchronized (jetty) { int count = latch.getAndIncrement(); if (count == 0) { @@ -32,7 +32,7 @@ static void start() { } } - static void stop() { + public static void stop() { synchronized (jetty) { int count = latch.decrementAndGet(); if (count == 0) { @@ -47,6 +47,9 @@ static void stop() { public static String map(Class<? extends BaseServlet> servletClass) { synchronized (jetty) { + if (!jetty.isStarted()) + start(); // if running out of the test cases + String path = "/" + servletClass.getSimpleName(); handler.addServletWithMapping(servletClass, path); int port = ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 6113f67d3f..5d6f546ab0 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.net.ConnectException; import java.net.InetSocketAddress; -import java.net.MalformedURLException; import java.net.Proxy; import java.net.URL; import java.util.List; @@ -40,20 +39,6 @@ public class UrlConnectTest { private static String echoURL = "http://direct.infohound.net/tools/q.pl"; public static String browserUa = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"; - @Test - public void fetchURl() throws IOException { - String url = "https://jsoup.org"; // no trailing / to force redir - Document doc = Jsoup.parse(new URL(url), 10*1000); - assertTrue(doc.title().contains("jsoup")); - } - - @Test - public void fetchURIWithWihtespace() throws IOException { - Connection con = Jsoup.connect("http://try.jsoup.org/#with whitespaces"); - Document doc = con.get(); - assertTrue(doc.title().contains("jsoup")); - } - @Test public void fetchBaidu() throws IOException { Connection.Response res = Jsoup.connect("http://www.baidu.com/").timeout(10*1000).execute(); @@ -81,124 +66,12 @@ public void exceptOnUnknownContentType() { assertTrue(threw); } - @Test - public void exceptOnUnsupportedProtocol(){ - String url = "file://etc/passwd"; - boolean threw = false; - try { - Document doc = Jsoup.connect(url).get(); - } catch (MalformedURLException e) { - threw = true; - assertEquals("java.net.MalformedURLException: Only http & https protocols supported", e.toString()); - } catch (IOException e) { - } - assertTrue(threw); - } - @Test public void ignoresContentTypeIfSoConfigured() throws IOException { Document doc = Jsoup.connect("https://jsoup.org/rez/osi_logo.png").ignoreContentType(true).get(); assertEquals("", doc.title()); // this will cause an ugly parse tree } - @Test - public void doesPost() throws IOException { - Document doc = Jsoup.connect(echoURL) - .data("uname", "Jsoup", "uname", "Jonathan", "百", "度一下") - .cookie("auth", "token") - .post(); - - assertEquals("POST", ihVal("REQUEST_METHOD", doc)); - //assertEquals("gzip", ihVal("HTTP_ACCEPT_ENCODING", doc)); // current proxy removes gzip on post - assertEquals("auth=token", ihVal("HTTP_COOKIE", doc)); - assertEquals("度一下", ihVal("百", doc)); - assertEquals("Jsoup, Jonathan", ihVal("uname", doc)); - } - - @Test - public void sendsRequestBodyJsonWithData() throws IOException { - final String body = "{key:value}"; - Document doc = Jsoup.connect(echoURL) - .requestBody(body) - .header("Content-Type", "application/json") - .userAgent(browserUa) - .data("foo", "true") - .post(); - assertEquals("POST", ihVal("REQUEST_METHOD", doc)); - assertEquals("application/json", ihVal("CONTENT_TYPE", doc)); - assertEquals("foo=true", ihVal("QUERY_STRING", doc)); - assertEquals(body, doc.select("th:contains(POSTDATA) ~ td").text()); - } - - @Test - public void sendsRequestBodyJsonWithoutData() throws IOException { - final String body = "{key:value}"; - Document doc = Jsoup.connect(echoURL) - .requestBody(body) - .header("Content-Type", "application/json") - .userAgent(browserUa) - .post(); - assertEquals("POST", ihVal("REQUEST_METHOD", doc)); - assertEquals("application/json", ihVal("CONTENT_TYPE", doc)); - assertEquals(body, doc.select("th:contains(POSTDATA) ~ td").text()); - } - - @Test - public void sendsRequestBody() throws IOException { - final String body = "{key:value}"; - Document doc = Jsoup.connect(echoURL) - .requestBody(body) - .header("Content-Type", "text/plain") - .userAgent(browserUa) - .post(); - assertEquals("POST", ihVal("REQUEST_METHOD", doc)); - assertEquals("text/plain", ihVal("CONTENT_TYPE", doc)); - assertEquals(body, doc.select("th:contains(POSTDATA) ~ td").text()); - } - - @Test - public void sendsRequestBodyWithUrlParams() throws IOException { - final String body = "{key:value}"; - Document doc = Jsoup.connect(echoURL) - .requestBody(body) - .data("uname", "Jsoup", "uname", "Jonathan", "百", "度一下") - .header("Content-Type", "text/plain") // todo - if user sets content-type, we should append postcharset - .userAgent(browserUa) - .post(); - assertEquals("POST", ihVal("REQUEST_METHOD", doc)); - assertEquals("uname=Jsoup&uname=Jonathan&%E7%99%BE=%E5%BA%A6%E4%B8%80%E4%B8%8B", ihVal("QUERY_STRING", doc)); - assertEquals(body, ihVal("POSTDATA", doc)); - } - - @Test - public void doesGet() throws IOException { - Connection con = Jsoup.connect(echoURL + "?what=the") - .userAgent("Mozilla") - .referrer("http://example.com") - .data("what", "about & me?"); - - Document doc = con.get(); - assertEquals("what=the&what=about+%26+me%3F", ihVal("QUERY_STRING", doc)); - assertEquals("the, about & me?", ihVal("what", doc)); - assertEquals("Mozilla", ihVal("HTTP_USER_AGENT", doc)); - assertEquals("http://example.com", ihVal("HTTP_REFERER", doc)); - } - - @Test - public void doesPut() throws IOException { - Connection.Response res = Jsoup.connect(echoURL) - .data("uname", "Jsoup", "uname", "Jonathan", "百", "度一下") - .cookie("auth", "token") - .method(Connection.Method.PUT) - .execute(); - - Document doc = res.parse(); - assertEquals("PUT", ihVal("REQUEST_METHOD", doc)); - //assertEquals("gzip", ihVal("HTTP_ACCEPT_ENCODING", doc)); // current proxy removes gzip on post - assertEquals("auth=token", ihVal("HTTP_COOKIE", doc)); - } - - private static String ihVal(String key, Document doc) { return doc.select("th:contains("+key+") + td").first().text(); } diff --git a/src/test/java/org/jsoup/integration/servlets/BaseServlet.java b/src/test/java/org/jsoup/integration/servlets/BaseServlet.java index 91276b2c17..b1bf5cca76 100644 --- a/src/test/java/org/jsoup/integration/servlets/BaseServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/BaseServlet.java @@ -7,10 +7,21 @@ import java.io.IOException; public abstract class BaseServlet extends HttpServlet { - static final String TextHtml = "text/html"; + static final String TextHtml = "text/html; charset=UTF-8"; + // these are overridden just to get the response name to be 'res' not 'resp' @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { super.doGet(req, res); } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + super.doPost(req, res); + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + super.doPut(req, res); + } } diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java new file mode 100644 index 0000000000..753c144b25 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -0,0 +1,90 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.helper.DataUtil; +import org.jsoup.helper.StringUtil; +import org.jsoup.integration.TestServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.util.Enumeration; + +import static org.jsoup.nodes.Entities.escape; + +public class EchoServlet extends BaseServlet { + public static final String Url = TestServer.map(EchoServlet.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doIt(req, res); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doIt(req, res); + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doIt(req, res); + } + + private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException { + res.setContentType(TextHtml); + res.setStatus(HttpServletResponse.SC_OK); + PrintWriter w = res.getWriter(); + + w.write("<title>Webserver Environment Variables\n" + + " \n" + + " \n" + + " "); + + // some get items + write(w, "Method", req.getMethod()); + write(w, "Request URI", req.getRequestURI()); + write(w, "Query String", req.getQueryString()); + + // request headers (why is it an enumeration?) + Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + Enumeration headers = req.getHeaders(header); + while (headers.hasMoreElements()) { + write(w, header, headers.nextElement()); + } + } + + // the request params + Enumeration parameterNames = req.getParameterNames(); + while (parameterNames.hasMoreElements()) { + String name = parameterNames.nextElement(); + String[] values = req.getParameterValues(name); + write(w, name, StringUtil.join(values, ", ")); + } + + // rest body + ByteBuffer byteBuffer = DataUtil.readToByteBuffer(req.getInputStream(), 0); + String postData = new String(byteBuffer.array(), "UTF-8"); + if (!StringUtil.isBlank(postData)) { + write(w, "Post Data", postData); + } + + w.println("
"); + } + + private static void write(PrintWriter w, String key, String val) { + w.println("" + escape(key) + "" + escape(val) + ""); + } + + // allow the servlet to run as a main program, for local test + public static void main(String[] args) { + TestServer.start(); + System.out.println(Url); + } +} From 7d8abb78a1d0c4cdfa93e7e96e9b2e625486e99f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Oct 2017 17:57:48 -0700 Subject: [PATCH 175/774] SlowRider test for interruptable read --- .../internal/ConstrainableInputStream.java | 8 ++- .../org/jsoup/integration/ConnectTest.java | 58 +++++++++++++++++++ .../org/jsoup/integration/UrlConnectTest.java | 54 +---------------- .../jsoup/integration/servlets/SlowRider.java | 44 ++++++++++++++ 4 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/servlets/SlowRider.java diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 5af2b9335b..95bf718c75 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -18,6 +18,7 @@ public final class ConstrainableInputStream extends BufferedInputStream { private final boolean capped; private final int maxSize; private int remaining; + private boolean interrupted; private ConstrainableInputStream(InputStream in, int bufferSize, int maxSize) { super(in, bufferSize); @@ -42,8 +43,13 @@ public static ConstrainableInputStream wrap(InputStream in, int bufferSize, int @Override public int read(byte[] b, int off, int len) throws IOException { - if (Thread.interrupted() || capped && remaining <= 0) + if (interrupted || capped && remaining <= 0) return -1; + if (Thread.interrupted()) { + // tracks if this read was interrupted, because parse() may call twice (and we still want the thread interupt to clear) + interrupted = true; + return -1; + } if (capped && len > remaining) len = remaining; // don't read more than desired, even if available diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 355a17ec63..3cf73e09e9 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -4,10 +4,12 @@ import org.jsoup.Jsoup; import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.HelloServlet; +import org.jsoup.integration.servlets.SlowRider; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import java.io.IOException; @@ -171,4 +173,60 @@ public void doesPut() throws IOException { assertEquals("gzip", ihVal("Accept-Encoding", doc)); assertEquals("auth=token", ihVal("Cookie", doc)); } + + // Slow Rider tests. Ignored by default so tests don't take aaages + @Ignore + @Test public void canInterruptBodyStringRead() throws IOException, InterruptedException { + // todo - implement in interruptable channels, so it's immediate + final String[] body = new String[1]; + Thread runner = new Thread(new Runnable() { + public void run() { + try { + Connection.Response res = Jsoup.connect(SlowRider.Url) + .timeout(15 * 1000) + .execute(); + body[0] = res.body(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + }); + + runner.start(); + Thread.sleep(1000 * 3); + runner.interrupt(); + assertTrue(runner.isInterrupted()); + runner.join(); + + assertTrue(body[0].length() > 0); + assertTrue(body[0].contains("

Are you still there?")); + } + + @Ignore + @Test public void canInterruptDocumentRead() throws IOException, InterruptedException { + // todo - implement in interruptable channels, so it's immediate + final String[] body = new String[1]; + Thread runner = new Thread(new Runnable() { + public void run() { + try { + Connection.Response res = Jsoup.connect(SlowRider.Url) + .timeout(15 * 1000) + .execute(); + body[0] = res.parse().text(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + }); + + runner.start(); + Thread.sleep(1000 * 3); + runner.interrupt(); + assertTrue(runner.isInterrupted()); + runner.join(); + + assertTrue(body[0].length() == 0); // doesn't ready a failed doc + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 5d6f546ab0..5599073e2b 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -654,59 +654,7 @@ public void inWildUtfRedirect2() throws IOException { ); } - @Test public void canInterruptBodyStringRead() throws IOException, InterruptedException { - // todo - implement in interruptable channels, so it's immediate - final String[] body = new String[1]; - Thread runner = new Thread(new Runnable() { - public void run() { - try { - Connection.Response res = Jsoup.connect("http://jsscxml.org/serverload.stream") - .timeout(15 * 1000) - .execute(); - body[0] = res.body(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - }); - - runner.start(); - Thread.sleep(1000 * 7); - runner.interrupt(); - assertTrue(runner.isInterrupted()); - runner.join(); - - assertTrue(body[0].length() > 0); - } - - @Test public void canInterruptDocumentRead() throws IOException, InterruptedException { - // todo - implement in interruptable channels, so it's immediate - final String[] body = new String[1]; - Thread runner = new Thread(new Runnable() { - public void run() { - try { - Connection.Response res = Jsoup.connect("http://jsscxml.org/serverload.stream") - .timeout(15 * 1000) - .execute(); - body[0] = res.parse().text(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - }); - - runner.start(); - Thread.sleep(1000 * 7); - runner.interrupt(); - assertTrue(runner.isInterrupted()); - runner.join(); - - assertTrue(body[0].length() > 0); - } - - @Test public void handlesEscapedRedirectUrls() throws IOException { + @Test public void handlesEscapedRedirectUrls() throws IOException { String url = "http://www.altalex.com/documents/news/2016/12/06/questioni-civilistiche-conseguenti-alla-depenalizzazione"; // sends: Location:http://shop.wki.it/shared/sso/sso.aspx?sso=&url=http%3a%2f%2fwww.altalex.com%2fsession%2fset%2f%3freturnurl%3dhttp%253a%252f%252fwww.altalex.com%253a80%252fdocuments%252fnews%252f2016%252f12%252f06%252fquestioni-civilistiche-conseguenti-alla-depenalizzazione // then to: http://www.altalex.com/session/set/?returnurl=http%3a%2f%2fwww.altalex.com%3a80%2fdocuments%2fnews%2f2016%2f12%2f06%2fquestioni-civilistiche-conseguenti-alla-depenalizzazione&sso=RDRG6T684G4AK2E7U591UGR923 diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java new file mode 100644 index 0000000000..e384fe7e36 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -0,0 +1,44 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.integration.TestServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Slowly, interminably writes output. For the purposes of testing timeouts and interrupts. + */ +public class SlowRider extends BaseServlet { + public static final String Url = TestServer.map(SlowRider.class); + private static final int SleepTime = 1000; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + res.setContentType(TextHtml); + res.setStatus(HttpServletResponse.SC_OK); + PrintWriter w = res.getWriter(); + + while (true) { + w.println("

Are you still there?"); + boolean err = w.checkError(); // flush and check still ok + if (err) { + log("Remote connection lost"); + break; + } + try { + Thread.sleep(SleepTime); + } catch (InterruptedException e) { + break; + } + } + } + + // allow the servlet to run as a main program, for local test + public static void main(String[] args) { + TestServer.start(); + System.out.println(Url); + } +} From a63fcab33707b7b466fbbf4ce4e7e63d787ba23e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Oct 2017 22:12:01 -0700 Subject: [PATCH 176/774] Implemented a proper total duration timeout --- CHANGES | 4 +++ src/main/java/org/jsoup/Connection.java | 11 +++--- .../java/org/jsoup/helper/HttpConnection.java | 9 +++-- .../internal/ConstrainableInputStream.java | 34 ++++++++++++++++--- .../org/jsoup/integration/ConnectTest.java | 30 ++++++++++++++++ .../jsoup/integration/servlets/SlowRider.java | 14 ++++++++ 6 files changed, 89 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index b31fb375e2..b83a5ebdb4 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,10 @@ jsoup changelog * Updated Element.text() and the :contains(text) selector to consider   character as spaces. + * Updated Jsoup.connect().timeout() to implement a total connect + combined read timeout. Previously it specified + connect and buffer read times only, so to implement a combined total timeout, you had to have another thread send + an interupt. + * Improved performance of Node.addChildren (was quadratic) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 6a6a0ea4a3..56a1078358 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -85,14 +85,13 @@ public final boolean hasBody() { Connection userAgent(String userAgent); /** - * Set the request timeouts (connect and read). If a timeout occurs, an IOException will be thrown. The default - * timeout is 30 seconds (30,000 millis). A timeout of zero is treated as an infinite timeout. - *

Note that a read timeout is not the same as a maximum timeout. As long as the connection is sending bytes at - * least every timeout seconds (e.g. in the case of an infinite stream of data, or a slow large download), the - * read timeout will not fire. This can be mitigated by using a maximum download size (see {@link #maxBodySize(int)}), - * or interrupting the connecting thread after a max timeout.

+ * Set the total request timeout duration. If a timeout occurs, an {@link java.net.SocketTimeoutException} will be thrown. + *

The default timeout is 30 seconds (30,000 millis). A timeout of zero is treated as an infinite timeout. + *

Note that this timeout specifies the combined maximum duration of the connection time and the time to read + * the full response. * @param millis number of milliseconds (thousandths of a second) before timing out connects or reads. * @return this Connection, for chaining + * @see #maxBodySize(int) */ Connection timeout(int millis); diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 75055b39d2..068316c68f 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -66,6 +66,7 @@ public class HttpConnection implements Connection { private static final String MULTIPART_FORM_DATA = "multipart/form-data"; private static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; private static final int HTTP_TEMP_REDIR = 307; // http/1.1 temporary redirect, not in Java's set. + private static final int ReadTimeoutMillis = 800; // max time between reads - only throws if exceeds total request timeout public static Connection connect(String url) { Connection con = new HttpConnection(); @@ -715,6 +716,7 @@ static Response execute(Connection.Request req, Response previousResponse) throw else if (methodHasBody) mimeBoundary = setOutputContentType(req); + long startTime = System.nanoTime(); HttpURLConnection conn = createConnection(req); Response res; try { @@ -774,7 +776,10 @@ else if (methodHasBody) res.bodyStream = conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream(); if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) res.bodyStream = new GZIPInputStream(res.bodyStream); - res.bodyStream = ConstrainableInputStream.wrap(res.bodyStream, DataUtil.bufferSize, req.maxBodySize()); + res.bodyStream = ConstrainableInputStream + .wrap(res.bodyStream, DataUtil.bufferSize, req.maxBodySize()) + .timeout(startTime, req.timeout()) + ; } else { res.byteData = DataUtil.emptyByteBuffer(); } @@ -881,7 +886,7 @@ private static HttpURLConnection createConnection(Connection.Request req) throws conn.setRequestMethod(req.method().name()); conn.setInstanceFollowRedirects(false); // don't rely on native redirection support conn.setConnectTimeout(req.timeout()); - conn.setReadTimeout(req.timeout()); + conn.setReadTimeout(ReadTimeoutMillis); if (conn instanceof HttpsURLConnection) { if (!req.validateTLSCertificates()) { diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 95bf718c75..7b3149f548 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -6,6 +6,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.SocketTimeoutException; import java.nio.ByteBuffer; /** @@ -17,6 +18,8 @@ public final class ConstrainableInputStream extends BufferedInputStream { private final boolean capped; private final int maxSize; + private long startTime; + private long timeout = -1; // optional max time of request private int remaining; private boolean interrupted; @@ -26,6 +29,7 @@ private ConstrainableInputStream(InputStream in, int bufferSize, int maxSize) { this.maxSize = maxSize; remaining = maxSize; capped = maxSize != 0; + startTime = System.nanoTime(); } /** @@ -46,18 +50,23 @@ public int read(byte[] b, int off, int len) throws IOException { if (interrupted || capped && remaining <= 0) return -1; if (Thread.interrupted()) { - // tracks if this read was interrupted, because parse() may call twice (and we still want the thread interupt to clear) + // interrupted latches, because parse() may call twice (and we still want the thread interupt to clear) interrupted = true; return -1; } + if (expired()) + throw new SocketTimeoutException("Read timeout"); if (capped && len > remaining) len = remaining; // don't read more than desired, even if available - final int read = super.read(b, off, len); - remaining -= read; - - return read; + try { + final int read = super.read(b, off, len); + remaining -= read; + return read; + } catch (SocketTimeoutException e) { + return 0; + } } /** @@ -94,4 +103,19 @@ public void reset() throws IOException { super.reset(); remaining = maxSize - markpos; } + + public ConstrainableInputStream timeout(long startTimeNanos, long timeoutMillis) { + this.startTime = startTimeNanos; + this.timeout = timeoutMillis * 1000000; + return this; + } + + private boolean expired() { + if (timeout == -1) + return false; + + final long now = System.nanoTime(); + final long dur = now - startTime; + return (dur > timeout); + } } diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 3cf73e09e9..fd93e0a75a 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.net.MalformedURLException; +import java.net.SocketTimeoutException; import java.net.URL; import static org.jsoup.integration.UrlConnectTest.browserUa; @@ -229,4 +230,33 @@ public void run() { assertTrue(body[0].length() == 0); // doesn't ready a failed doc } + + @Ignore + @Test public void totalTimeout() throws IOException { + int timeout = 3 * 1000; + long start = System.currentTimeMillis(); + boolean threw = false; + try { + Jsoup.connect(SlowRider.Url).timeout(timeout).get(); + } catch (SocketTimeoutException e) { + long end = System.currentTimeMillis(); + long took = end - start; + assertTrue(("Time taken was " + took), took > timeout); + assertTrue(("Time taken was " + took), took < timeout * 1.2); + threw = true; + } + + assertTrue(threw); + } + + @Ignore + @Test public void slowReadOk() throws IOException { + // make sure that a slow read that is under the request timeout is still OK + Document doc = Jsoup.connect(SlowRider.Url) + .data(SlowRider.MaxTimeParam, "2000") // the reqest completes in 2 seconds + .get(); + + Element h1 = doc.selectFirst("h1"); + assertEquals("outatime", h1.text()); + } } diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java index e384fe7e36..64fd0e4a91 100644 --- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -14,6 +14,8 @@ public class SlowRider extends BaseServlet { public static final String Url = TestServer.map(SlowRider.class); private static final int SleepTime = 1000; + public static String MaxTimeParam = "maxTime"; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { @@ -21,6 +23,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser res.setStatus(HttpServletResponse.SC_OK); PrintWriter w = res.getWriter(); + int maxTime = -1; + String maxTimeP = req.getParameter(MaxTimeParam); + if (maxTimeP != null) { + maxTime = Integer.valueOf(maxTimeP); + } + + long startTime = System.currentTimeMillis(); while (true) { w.println("

Are you still there?"); boolean err = w.checkError(); // flush and check still ok @@ -33,6 +42,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser } catch (InterruptedException e) { break; } + + if (maxTime > 0 && System.currentTimeMillis() > startTime + maxTime) { + w.println("

outatime

"); + break; + } } } From 2b488261f65629bca62d633057566b1dfdd3038a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Oct 2017 18:14:15 -0700 Subject: [PATCH 177/774] Simplify --- src/main/java/org/jsoup/parser/Tokeniser.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 21a1fac026..1b4aa8475e 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -221,9 +221,7 @@ boolean isAppropriateEndTagToken() { } String appropriateEndTagName() { - if (lastStartTag == null) - return null; - return lastStartTag; + return lastStartTag; // could be null } void error(TokeniserState state) { From abfeea204ea0337d76931c2adfb26e62b39cdf13 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 16 Oct 2017 11:26:29 -0700 Subject: [PATCH 178/774] Test scope Jetty --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 000c05b778..dcc203454d 100644 --- a/pom.xml +++ b/pom.xml @@ -227,6 +227,7 @@ org.eclipse.jetty jetty-server 9.2.22.v20170606 + test @@ -234,10 +235,9 @@ org.eclipse.jetty jetty-servlet 9.2.22.v20170606 + test - - From 6bce3ded2afd743f1727564f17a6d46200ddd32a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 19 Oct 2017 22:42:16 -0700 Subject: [PATCH 179/774] Tidy up --- src/test/java/org/jsoup/integration/servlets/SlowRider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java index 64fd0e4a91..6c9e2a7157 100644 --- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -14,7 +14,7 @@ public class SlowRider extends BaseServlet { public static final String Url = TestServer.map(SlowRider.class); private static final int SleepTime = 1000; - public static String MaxTimeParam = "maxTime"; + public static final String MaxTimeParam = "maxTime"; @Override @@ -32,7 +32,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser long startTime = System.currentTimeMillis(); while (true) { w.println("

Are you still there?"); - boolean err = w.checkError(); // flush and check still ok + boolean err = w.checkError(); // flush, and check still ok if (err) { log("Remote connection lost"); break; From 4685bd091a690e219e629687bf84cc6f752eff81 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Oct 2017 16:53:32 -0700 Subject: [PATCH 180/774] Add option to set mimetype on uploads --- CHANGES | 3 ++ src/main/java/org/jsoup/Connection.java | 31 +++++++++++++- .../java/org/jsoup/helper/HttpConnection.java | 24 ++++++++++- .../org/jsoup/integration/ConnectTest.java | 41 +++++++++++++++++++ .../org/jsoup/integration/UrlConnectTest.java | 18 +------- .../integration/servlets/EchoServlet.java | 36 +++++++++++++++- 6 files changed, 132 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index b83a5ebdb4..2a54285f0a 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,9 @@ jsoup changelog * Added missing support for template tags in tables + * In Jsoup.connect file uploads, added the ability to set the uploaded files' mimetype. + + * Improved Node traversal, including less object creation, and partial and filtering traversor support. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 56a1078358..76d9bcf0c5 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -181,9 +181,23 @@ public final boolean hasBody() { * @param inputStream the input stream to upload, that you probably obtained from a {@link java.io.FileInputStream}. * You must close the InputStream in a {@code finally} block. * @return this Connections, for chaining + * @see #data(String, String, InputStream, String) if you want to set the uploaded file's mimetype. */ Connection data(String key, String filename, InputStream inputStream); + /** + * Add an input stream as a request data parameter. For GETs, has no effect, but for POSTS this will upload the + * input stream. + * @param key data key (form item name) + * @param filename the name of the file to present to the remove server. Typically just the name, not path, + * component. + * @param inputStream the input stream to upload, that you probably obtained from a {@link java.io.FileInputStream}. + * @param contentType the Content Type (aka mimetype) to specify for this file. + * You must close the InputStream in a {@code finally} block. + * @return this Connections, for chaining + */ + Connection data(String key, String filename, InputStream inputStream, String contentType); + /** * Adds all of the supplied data to the request data parameters * @param data collection of data parameters @@ -718,7 +732,7 @@ interface Response extends Base { } /** - * A Key Value tuple. + * A Key:Value tuple(+), used for form data. */ interface KeyVal { @@ -766,5 +780,20 @@ interface KeyVal { * @return true if this keyval does indeed have an input stream */ boolean hasInputStream(); + + /** + * Set the Content Type header used in the MIME body (aka mimetype) when uploading files. + * Only useful if {@link #inputStream(InputStream)} is set. + *

Will default to {@code application/octet-stream}.

+ * @param contentType the new content type + * @return this KeyVal + */ + KeyVal contentType(String contentType); + + /** + * Get the current Content Type, or {@code null} if not set. + * @return the current Content Type. + */ + String contentType(); } } diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 068316c68f..4bfb80fe4e 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -67,6 +67,7 @@ public class HttpConnection implements Connection { private static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; private static final int HTTP_TEMP_REDIR = 307; // http/1.1 temporary redirect, not in Java's set. private static final int ReadTimeoutMillis = 800; // max time between reads - only throws if exceeds total request timeout + private static final String DefaultUploadType = "application/octet-stream"; public static Connection connect(String url) { Connection con = new HttpConnection(); @@ -202,6 +203,12 @@ public Connection data(String key, String filename, InputStream inputStream) { return this; } + @Override + public Connection data(String key, String filename, InputStream inputStream, String contentType) { + req.data(KeyVal.create(key, filename, inputStream).contentType(contentType)); + return this; + } + public Connection data(Map data) { Validate.notNull(data, "Data map must not be null"); for (Map.Entry entry : data.entrySet()) { @@ -1078,7 +1085,9 @@ private static void writePost(final Connection.Request req, final OutputStream o if (keyVal.hasInputStream()) { w.write("; filename=\""); w.write(encodeMimeName(keyVal.value())); - w.write("\"\r\nContent-Type: application/octet-stream\r\n\r\n"); + w.write("\"\r\nContent-Type: "); + w.write(keyVal.contentType() != null ? keyVal.contentType() : DefaultUploadType); + w.write("\r\n\r\n"); w.flush(); // flush DataUtil.crossStreams(keyVal.inputStream(), outputStream); outputStream.flush(); @@ -1174,6 +1183,7 @@ public static class KeyVal implements Connection.KeyVal { private String key; private String value; private InputStream stream; + private String contentType; public static KeyVal create(String key, String value) { return new KeyVal().key(key).value(value); @@ -1219,6 +1229,18 @@ public boolean hasInputStream() { return stream != null; } + @Override + public Connection.KeyVal contentType(String contentType) { + Validate.notEmpty(contentType); + this.contentType = contentType; + return this; + } + + @Override + public String contentType() { + return contentType; + } + @Override public String toString() { return key + "=" + value; diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index fd93e0a75a..2fd51c5842 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -12,6 +12,8 @@ import org.junit.Ignore; import org.junit.Test; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.SocketTimeoutException; @@ -259,4 +261,43 @@ public void run() { Element h1 = doc.selectFirst("h1"); assertEquals("outatime", h1.text()); } + + /** + * Tests upload of content to a remote service. + */ + @Test + public void postFiles() throws IOException { + File thumb = ParseTest.getFile("/htmltests/thumb.jpg"); + File html = ParseTest.getFile("/htmltests/google-ipod.html"); + + Document res = Jsoup + .connect(EchoServlet.Url) + .data("firstPart", thumb.getName(), new FileInputStream(thumb), "image/jpeg") + .data("secondPart", html.getName(), new FileInputStream(html)) // defaults to "application-octetstream"; + .proxy("localhost", 8888) + .post(); + + assertEquals("2", ihVal("Parts", res)); + + assertEquals("application/octet-stream", ihVal("Part secondPart ContentType", res)); + assertEquals("secondPart", ihVal("Part secondPart Name", res)); + assertEquals("google-ipod.html", ihVal("Part secondPart Filename", res)); + assertEquals("43972", ihVal("Part secondPart Size", res)); + + assertEquals("image/jpeg", ihVal("Part firstPart ContentType", res)); + assertEquals("firstPart", ihVal("Part firstPart Name", res)); + assertEquals("thumb.jpg", ihVal("Part firstPart Filename", res)); + assertEquals("1052", ihVal("Part firstPart Size", res)); + + /* + Part secondPart ContentTypeapplication/octet-stream + Part secondPart NamesecondPart + Part secondPart Filenamegoogle-ipod.html + Part secondPart Size43972 + Part firstPart ContentTypeimage/jpeg + Part firstPart NamefirstPart + Part firstPart Filenamethumb.jpg + Part firstPart Size1052 + */ + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 5599073e2b..ba22391196 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -409,7 +409,7 @@ public void postHtmlFile() throws IOException { File uploadFile = ParseTest.getFile("/htmltests/google-ipod.html"); FileInputStream stream = new FileInputStream(uploadFile); - + Connection.KeyVal fileData = post.data("_file"); fileData.value("check.html"); fileData.inputStream(stream); @@ -425,22 +425,6 @@ public void postHtmlFile() throws IOException { assertTrue(out.text().contains("HTML Tidy Complete")); } - /** - * Tests upload of binary content to a remote service. - */ - @Test - public void postJpeg() throws IOException { - File thumb = ParseTest.getFile("/htmltests/thumb.jpg"); - Document result = Jsoup - .connect("http://regex.info/exif.cgi") - .data("f", thumb.getName(), new FileInputStream(thumb)) - .userAgent(browserUa) - .post(); - - assertEquals("Baseline DCT, Huffman coding", result.select("td:contains(Process) + td").text()); - assertEquals("1052 bytes 30 × 30", result.select("td:contains(Size) + td").text()); - } - @Test public void handles201Created() throws IOException { Document doc = Jsoup.connect("http://direct.infohound.net/tools/201.pl").get(); // 201, location=jsoup diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java index 753c144b25..ef2f5e286c 100644 --- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -1,15 +1,19 @@ package org.jsoup.integration.servlets; +import org.eclipse.jetty.server.Request; import org.jsoup.helper.DataUtil; import org.jsoup.helper.StringUtil; import org.jsoup.integration.TestServer; +import javax.servlet.MultipartConfigElement; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; +import java.util.Collection; import java.util.Enumeration; import static org.jsoup.nodes.Entities.escape; @@ -32,7 +36,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse res) throws Ser doIt(req, res); } - private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException { + private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { + boolean isMulti = maybeEnableMultipart(req); + res.setContentType(TextHtml); res.setStatus(HttpServletResponse.SC_OK); PrintWriter w = res.getWriter(); @@ -68,13 +74,28 @@ private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOExce write(w, name, StringUtil.join(values, ", ")); } - // rest body + // post body ByteBuffer byteBuffer = DataUtil.readToByteBuffer(req.getInputStream(), 0); String postData = new String(byteBuffer.array(), "UTF-8"); if (!StringUtil.isBlank(postData)) { write(w, "Post Data", postData); } + // file uploads + if (isMulti) { + Collection parts = req.getParts(); + write(w, "Parts", String.valueOf(parts.size())); + + for (Part part : parts) { + String name = part.getName(); + write(w, "Part " + name + " ContentType", part.getContentType()); + write(w, "Part " + name + " Name", name); + write(w, "Part " + name + " Filename", part.getSubmittedFileName()); + write(w, "Part " + name + " Size", String.valueOf(part.getSize())); + part.delete(); + } + } + w.println(""); } @@ -87,4 +108,15 @@ public static void main(String[] args) { TestServer.start(); System.out.println(Url); } + + private static boolean maybeEnableMultipart(HttpServletRequest req) { + boolean isMulti = req.getContentType() != null + && req.getContentType().startsWith("multipart/form-data"); + + if (isMulti) { + req.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement( + System.getProperty("java.io.tmpdir"))); + } + return isMulti; + } } From 5ba643ce7b1b08757f43c0f6e173937be39ea0d7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Oct 2017 17:05:08 -0700 Subject: [PATCH 181/774] Proxy should not be in the test --- src/test/java/org/jsoup/integration/ConnectTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 2fd51c5842..49eacf2301 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -274,7 +274,6 @@ public void postFiles() throws IOException { .connect(EchoServlet.Url) .data("firstPart", thumb.getName(), new FileInputStream(thumb), "image/jpeg") .data("secondPart", html.getName(), new FileInputStream(html)) // defaults to "application-octetstream"; - .proxy("localhost", 8888) .post(); assertEquals("2", ihVal("Parts", res)); From 2412188026aa32491e769d6221c16a3bda6e897b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Oct 2017 17:17:23 -0700 Subject: [PATCH 182/774] Fixup crlf size --- src/test/java/org/jsoup/integration/ConnectTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 49eacf2301..c463263431 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -281,7 +281,12 @@ public void postFiles() throws IOException { assertEquals("application/octet-stream", ihVal("Part secondPart ContentType", res)); assertEquals("secondPart", ihVal("Part secondPart Name", res)); assertEquals("google-ipod.html", ihVal("Part secondPart Filename", res)); - assertEquals("43972", ihVal("Part secondPart Size", res)); + assertEquals("43963", ihVal("Part secondPart Size", res)); + // if this is failing as 43972 it is because git has normalized the html line endings to crlf (windows) + // disable that: + // git config --global core.eol lf + // git config --global core.autocrlf input + // (and rm cached and reset) assertEquals("image/jpeg", ihVal("Part firstPart ContentType", res)); assertEquals("firstPart", ihVal("Part firstPart Name", res)); From fb8b60b4d3d202c6fa708f60b8b4a5a53836af24 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Oct 2017 19:37:42 -0700 Subject: [PATCH 183/774] Limit stack search depth to 100 Fixes #955 --- CHANGES | 3 + .../java/org/jsoup/helper/StringUtil.java | 12 +++- .../org/jsoup/parser/HtmlTreeBuilder.java | 56 +++++++++++-------- .../jsoup/parser/HtmlTreeBuilderState.java | 50 +++++++++-------- .../org/jsoup/integration/UrlConnectTest.java | 13 +++++ .../java/org/jsoup/parser/HtmlParserTest.java | 23 ++++++++ 6 files changed, 109 insertions(+), 48 deletions(-) diff --git a/CHANGES b/CHANGES index 2a54285f0a..085782c9c7 100644 --- a/CHANGES +++ b/CHANGES @@ -74,6 +74,9 @@ jsoup changelog * Bugfix: fixed an issue where Element.getElementsByIndexLessThan(index) would incorrectly provide the root element + * Improved parse time for pages with exceptionally deeply nested tags. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index 68126046c0..60f9122a82 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -168,9 +168,10 @@ public static void appendNormalisedWhitespace(StringBuilder accum, String string } } - public static boolean in(String needle, String... haystack) { - for (String hay : haystack) { - if (hay.equals(needle)) + public static boolean in(final String needle, final String... haystack) { + final int len = haystack.length; + for (int i = 0; i < len; i++) { + if (haystack[i].equals(needle)) return true; } return false; @@ -180,6 +181,11 @@ public static boolean inSorted(String needle, String[] haystack) { return Arrays.binarySearch(haystack, needle) >= 0; } + public static String[] sort(String[] array) { + Arrays.sort(array); + return array; + } + /** * Create a new absolute URL, from a provided existing absolute URL and a relative URL component. * @param base the existing absolute base URL diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 041dcbd9b5..011a704118 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -16,25 +16,30 @@ import java.util.ArrayList; import java.util.List; +import static org.jsoup.helper.StringUtil.inSorted; +import static org.jsoup.helper.StringUtil.sort; + /** * HTML Tree Builder; creates a DOM from Tokens. */ public class HtmlTreeBuilder extends TreeBuilder { - // tag searches - private static final String[] TagsSearchInScope = new String[]{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}; - private static final String[] TagSearchList = new String[]{"ol", "ul"}; - private static final String[] TagSearchButton = new String[]{"button"}; - private static final String[] TagSearchTableScope = new String[]{"html", "table"}; - private static final String[] TagSearchSelectScope = new String[]{"optgroup", "option"}; - private static final String[] TagSearchEndTags = new String[]{"dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"}; - private static final String[] TagSearchSpecial = new String[]{"address", "applet", "area", "article", "aside", "base", "basefont", "bgsound", - "blockquote", "body", "br", "button", "caption", "center", "col", "colgroup", "command", "dd", - "details", "dir", "div", "dl", "dt", "embed", "fieldset", "figcaption", "figure", "footer", "form", - "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", - "iframe", "img", "input", "isindex", "li", "link", "listing", "marquee", "menu", "meta", "nav", - "noembed", "noframes", "noscript", "object", "ol", "p", "param", "plaintext", "pre", "script", - "section", "select", "style", "summary", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", - "title", "tr", "ul", "wbr", "xmp"}; + // tag searches. must be sorted, used in inSorted + private static final String[] TagsSearchInScope = sort(new String[]{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}); + private static final String[] TagSearchList = sort(new String[]{"ol", "ul"}); + private static final String[] TagSearchButton = sort(new String[]{"button"}); + private static final String[] TagSearchTableScope = sort(new String[]{"html", "table"}); + private static final String[] TagSearchSelectScope = sort(new String[]{"optgroup", "option"}); + private static final String[] TagSearchEndTags = sort(new String[]{"dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"}); + private static final String[] TagSearchSpecial = sort(new String[]{"address", "applet", "area", "article", "aside", "base", "basefont", "bgsound", + "blockquote", "body", "br", "button", "caption", "center", "col", "colgroup", "command", "dd", + "details", "dir", "div", "dl", "dt", "embed", "fieldset", "figcaption", "figure", "footer", "form", + "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", + "iframe", "img", "input", "isindex", "li", "link", "listing", "marquee", "menu", "meta", "nav", + "noembed", "noframes", "noscript", "object", "ol", "p", "param", "plaintext", "pre", "script", + "section", "select", "style", "summary", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", + "title", "tr", "ul", "wbr", "xmp"}); + + public static final int MaxScopeSearchDepth = 100; // prevents the parser bogging down in exceptionally broken pages private HtmlTreeBuilderState state; // the current state private HtmlTreeBuilderState originalState; // original / marked state @@ -332,11 +337,12 @@ void popStackToClose(String elName) { } } + // elnames is sorted, comes from Constants void popStackToClose(String... elNames) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); stack.remove(pos); - if (StringUtil.in(next.nodeName(), elNames)) + if (inSorted(next.nodeName(), elNames)) break; } } @@ -459,14 +465,18 @@ private boolean inSpecificScope(String targetName, String[] baseTypes, String[] } private boolean inSpecificScope(String[] targetNames, String[] baseTypes, String[] extraTypes) { - for (int pos = stack.size() -1; pos >= 0; pos--) { + int depth = stack.size() -1; + if (depth > MaxScopeSearchDepth) { + depth = MaxScopeSearchDepth; + } + for (int pos = depth; pos >= 0; pos--) { Element el = stack.get(pos); String elName = el.nodeName(); - if (StringUtil.in(elName, targetNames)) + if (inSorted(elName, targetNames)) return true; - if (StringUtil.in(elName, baseTypes)) + if (inSorted(elName, baseTypes)) return false; - if (extraTypes != null && StringUtil.in(elName, extraTypes)) + if (extraTypes != null && inSorted(elName, extraTypes)) return false; } Validate.fail("Should not be reachable"); @@ -505,7 +515,7 @@ boolean inSelectScope(String targetName) { String elName = el.nodeName(); if (elName.equals(targetName)) return true; - if (!StringUtil.in(elName, TagSearchSelectScope)) // all elements except + if (!inSorted(elName, TagSearchSelectScope)) // all elements except return false; } Validate.fail("Should not be reachable"); @@ -559,7 +569,7 @@ void setPendingTableCharacters(List pendingTableCharacters) { */ void generateImpliedEndTags(String excludeTag) { while ((excludeTag != null && !currentElement().nodeName().equals(excludeTag)) && - StringUtil.in(currentElement().nodeName(), TagSearchEndTags)) + inSorted(currentElement().nodeName(), TagSearchEndTags)) pop(); } @@ -571,7 +581,7 @@ boolean isSpecial(Element el) { // todo: mathml's mi, mo, mn // todo: svg's foreigObject, desc, title String name = el.nodeName(); - return StringUtil.in(name, TagSearchSpecial); + return inSorted(name, TagSearchSpecial); } Element lastFormattingElement() { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index c4924e42bc..3d869d6d0f 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1,7 +1,12 @@ package org.jsoup.parser; import org.jsoup.helper.StringUtil; -import org.jsoup.nodes.*; +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.DocumentType; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; import java.util.ArrayList; @@ -1499,27 +1504,28 @@ private static void handleRawtext(Token.StartTag startTag, HtmlTreeBuilder tb) { // lists of tags to search through. A little harder to read here, but causes less GC than dynamic varargs. // was contributing around 10% of parse GC load. + // must make sure these are sorted, as used in findSorted private static final class Constants { - private static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"}; - private static final String[] InBodyStartPClosers = new String[]{"address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", - "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", - "p", "section", "summary", "ul"}; - private static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; - private static final String[] InBodyStartPreListing = new String[]{"pre", "listing"}; - private static final String[] InBodyStartLiBreakers = new String[]{"address", "div", "p"}; - private static final String[] DdDt = new String[]{"dd", "dt"}; - private static final String[] Formatters = new String[]{"b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u"}; - private static final String[] InBodyStartApplets = new String[]{"applet", "marquee", "object"}; - private static final String[] InBodyStartEmptyFormatters = new String[]{"area", "br", "embed", "img", "keygen", "wbr"}; - private static final String[] InBodyStartMedia = new String[]{"param", "source", "track"}; - private static final String[] InBodyStartInputAttribs = new String[]{"name", "action", "prompt"}; - private static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; - private static final String[] InBodyStartRuby = new String[]{"rp", "rt"}; - private static final String[] InBodyStartDrop = new String[]{"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"}; - private static final String[] InBodyEndClosers = new String[]{"address", "article", "aside", "blockquote", "button", "center", "details", "dir", "div", - "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "menu", - "nav", "ol", "pre", "section", "summary", "ul"}; - private static final String[] InBodyEndAdoptionFormatters = new String[]{"a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u"}; - private static final String[] InBodyEndTableFosters = new String[]{"table", "tbody", "tfoot", "thead", "tr"}; + private static final String[] InBodyStartToHead = StringUtil.sort(new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"}); + private static final String[] InBodyStartPClosers = StringUtil.sort(new String[]{"address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", + "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", + "p", "section", "summary", "ul"}); + private static final String[] Headings = StringUtil.sort(new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}); + private static final String[] InBodyStartPreListing = StringUtil.sort(new String[]{"pre", "listing"}); + private static final String[] InBodyStartLiBreakers = StringUtil.sort(new String[]{"address", "div", "p"}); + private static final String[] DdDt = StringUtil.sort(new String[]{"dd", "dt"}); + private static final String[] Formatters = StringUtil.sort(new String[]{"b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u"}); + private static final String[] InBodyStartApplets = StringUtil.sort(new String[]{"applet", "marquee", "object"}); + private static final String[] InBodyStartEmptyFormatters = StringUtil.sort(new String[]{"area", "br", "embed", "img", "keygen", "wbr"}); + private static final String[] InBodyStartMedia = StringUtil.sort(new String[]{"param", "source", "track"}); + private static final String[] InBodyStartInputAttribs = StringUtil.sort(new String[]{"name", "action", "prompt"}); + private static final String[] InBodyStartOptions = StringUtil.sort(new String[]{"optgroup", "option"}); + private static final String[] InBodyStartRuby = StringUtil.sort(new String[]{"rp", "rt"}); + private static final String[] InBodyStartDrop = StringUtil.sort(new String[]{"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"}); + private static final String[] InBodyEndClosers = StringUtil.sort(new String[]{"address", "article", "aside", "blockquote", "button", "center", "details", "dir", "div", + "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "menu", + "nav", "ol", "pre", "section", "summary", "ul"}); + private static final String[] InBodyEndAdoptionFormatters = StringUtil.sort(new String[]{"a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u"}); + private static final String[] InBodyEndTableFosters = StringUtil.sort(new String[]{"table", "tbody", "tfoot", "thead", "tr"}); } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index ba22391196..836b82b233 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -697,4 +697,17 @@ public void inWildUtfRedirect2() throws IOException { assertTrue(doc2.title().contains("Environment")); } + @Test public void handlesSuperDeepPage() throws IOException { + // https://github.com/jhy/jsoup/issues/955 + + long start = System.currentTimeMillis(); + String url = "http://sv.stargate.wikia.com/wiki/M2J"; + Document doc = Jsoup.connect(url).get(); + assertEquals("M2J | Sv.stargate Wiki | FANDOM powered by Wikia", doc.title()); + assertEquals(110160, doc.select("dd").size()); + // those are all
stacked in each other. wonder how that got generated? + assertTrue(System.currentTimeMillis() - start < 1000); + + } + } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index a2cb069efc..afd2ee2786 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -901,6 +901,29 @@ public class HtmlParserTest { assertTrue(System.currentTimeMillis() - start < 1000); } + @Test public void handlesDeepStack() { + // inspired by http://sv.stargate.wikia.com/wiki/M2J and https://github.com/jhy/jsoup/issues/955 + // I didn't put it in the integration tests, because explorer and intellij kept dieing trying to preview/index it + + // Arrange + StringBuilder longBody = new StringBuilder(500000); + for (int i = 0; i < 25000; i++) { + longBody.append(i).append("
"); + } + for (int i = 0; i < 25000; i++) { + longBody.append(i).append("
"); + } + + // Act + long start = System.currentTimeMillis(); + Document doc = Parser.parseBodyFragment(longBody.toString(), ""); + + // Assert + assertEquals(2, doc.body().childNodeSize()); + assertEquals(25000, doc.select("dd").size()); + assertTrue(System.currentTimeMillis() - start < 1000); + } + @Test public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/table-invalid-elements.html"); From 72edc30eeda8ef001c043cc4ca173c6311bec887 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 21 Oct 2017 19:44:39 -0700 Subject: [PATCH 184/774] Moved more tests to the local server --- .../org/jsoup/integration/ConnectTest.java | 51 ++++++++++++++++++ .../org/jsoup/integration/UrlConnectTest.java | 52 ------------------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index c463263431..3d55a0ac43 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -18,6 +18,7 @@ import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; +import java.util.Map; import static org.jsoup.integration.UrlConnectTest.browserUa; import static org.junit.Assert.assertEquals; @@ -304,4 +305,54 @@ public void postFiles() throws IOException { Part firstPart Size1052 */ } + + @Test public void multipleParsesOkAfterBufferUp() throws IOException { + Connection.Response res = Jsoup.connect(echoUrl).execute().bufferUp(); + + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + + Document doc2 = res.parse(); + assertTrue(doc2.title().contains("Environment")); + } + + @Test(expected=IllegalArgumentException.class) public void bodyAfterParseThrowsValidationError() throws IOException { + Connection.Response res = Jsoup.connect(echoUrl).execute(); + Document doc = res.parse(); + String body = res.body(); + } + + @Test public void bodyAndBytesAvailableBeforeParse() throws IOException { + Connection.Response res = Jsoup.connect(echoUrl).execute(); + String body = res.body(); + assertTrue(body.contains("Environment")); + byte[] bytes = res.bodyAsBytes(); + assertTrue(bytes.length > 100); + + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + } + + @Test(expected=IllegalArgumentException.class) public void parseParseThrowsValidates() throws IOException { + Connection.Response res = Jsoup.connect(echoUrl).execute(); + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + Document doc2 = res.parse(); // should blow up because the response input stream has been drained + } + + + @Test + public void multiCookieSet() throws IOException { + Connection con = Jsoup.connect("http://direct.infohound.net/tools/302-cookie.pl"); + Connection.Response res = con.execute(); + + // test cookies set by redirect: + Map cookies = res.cookies(); + assertEquals("asdfg123", cookies.get("token")); + assertEquals("jhy", cookies.get("uid")); + + // send those cookies into the echo URL by map: + Document doc = Jsoup.connect(echoUrl).cookies(cookies).get(); + assertEquals("token=asdfg123; uid=jhy", ihVal("Cookie", doc)); + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 836b82b233..02e97ad342 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -22,7 +22,6 @@ import java.net.Proxy; import java.net.URL; import java.util.List; -import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -36,7 +35,6 @@ public class UrlConnectTest { private static final String WEBSITE_WITH_INVALID_CERTIFICATE = "https://certs.cac.washington.edu/CAtest/"; private static final String WEBSITE_WITH_SNI = "https://jsoup.org/"; - private static String echoURL = "http://direct.infohound.net/tools/q.pl"; public static String browserUa = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"; @Test @@ -249,21 +247,6 @@ public void maximumRedirects() { assertTrue(threw); } - @Test - public void multiCookieSet() throws IOException { - Connection con = Jsoup.connect("http://direct.infohound.net/tools/302-cookie.pl"); - Connection.Response res = con.execute(); - - // test cookies set by redirect: - Map cookies = res.cookies(); - assertEquals("asdfg123", cookies.get("token")); - assertEquals("jhy", cookies.get("uid")); - - // send those cookies into the echo URL by map: - Document doc = Jsoup.connect(echoURL).cookies(cookies).get(); - assertEquals("token=asdfg123; uid=jhy", ihVal("HTTP_COOKIE", doc)); - } - @Test public void handlesDodgyCharset() throws IOException { // tests that when we get back "UFT8", that it is recognised as unsupported, and falls back to default instead @@ -663,40 +646,6 @@ public void inWildUtfRedirect2() throws IOException { assertEquals("Index of /archiv/TV/A/%23No.Title", doc.title()); } - @Test(expected=IllegalArgumentException.class) public void bodyAfterParseThrowsValidationError() throws IOException { - Connection.Response res = Jsoup.connect(echoURL).execute(); - Document doc = res.parse(); - String body = res.body(); - } - - @Test public void bodyAndBytesAvailableBeforeParse() throws IOException { - Connection.Response res = Jsoup.connect(echoURL).execute(); - String body = res.body(); - assertTrue(body.contains("Environment")); - byte[] bytes = res.bodyAsBytes(); - assertTrue(bytes.length > 100); - - Document doc = res.parse(); - assertTrue(doc.title().contains("Environment")); - } - - @Test(expected=IllegalArgumentException.class) public void parseParseThrowsValidates() throws IOException { - Connection.Response res = Jsoup.connect(echoURL).execute(); - Document doc = res.parse(); - assertTrue(doc.title().contains("Environment")); - Document doc2 = res.parse(); // should blow up because the response input stream has been drained - } - - @Test public void multipleParsesOkAfterBufferUp() throws IOException { - Connection.Response res = Jsoup.connect(echoURL).execute().bufferUp(); - - Document doc = res.parse(); - assertTrue(doc.title().contains("Environment")); - - Document doc2 = res.parse(); - assertTrue(doc2.title().contains("Environment")); - } - @Test public void handlesSuperDeepPage() throws IOException { // https://github.com/jhy/jsoup/issues/955 @@ -707,7 +656,6 @@ public void inWildUtfRedirect2() throws IOException { assertEquals(110160, doc.select("dd").size()); // those are all
stacked in each other. wonder how that got generated? assertTrue(System.currentTimeMillis() - start < 1000); - } } From 24de855a1bc860ed8720b9941277e76c4034cf9c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 26 Oct 2017 13:41:11 -0700 Subject: [PATCH 185/774] Sort and validate sorted at build not runtime --- .../java/org/jsoup/helper/StringUtil.java | 5 --- .../org/jsoup/parser/HtmlTreeBuilder.java | 19 ++++----- .../jsoup/parser/HtmlTreeBuilderState.java | 42 +++++++++---------- .../parser/HtmlTreeBuilderStateTest.java | 39 +++++++++++++++++ .../org/jsoup/parser/HtmlTreeBuilderTest.java | 28 +++++++++++++ 5 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java create mode 100644 src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index 60f9122a82..c9bf750b65 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -181,11 +181,6 @@ public static boolean inSorted(String needle, String[] haystack) { return Arrays.binarySearch(haystack, needle) >= 0; } - public static String[] sort(String[] array) { - Arrays.sort(array); - return array; - } - /** * Create a new absolute URL, from a provided existing absolute URL and a relative URL component. * @param base the existing absolute base URL diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 011a704118..8eb3904ce7 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -17,27 +17,26 @@ import java.util.List; import static org.jsoup.helper.StringUtil.inSorted; -import static org.jsoup.helper.StringUtil.sort; /** * HTML Tree Builder; creates a DOM from Tokens. */ public class HtmlTreeBuilder extends TreeBuilder { - // tag searches. must be sorted, used in inSorted - private static final String[] TagsSearchInScope = sort(new String[]{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}); - private static final String[] TagSearchList = sort(new String[]{"ol", "ul"}); - private static final String[] TagSearchButton = sort(new String[]{"button"}); - private static final String[] TagSearchTableScope = sort(new String[]{"html", "table"}); - private static final String[] TagSearchSelectScope = sort(new String[]{"optgroup", "option"}); - private static final String[] TagSearchEndTags = sort(new String[]{"dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"}); - private static final String[] TagSearchSpecial = sort(new String[]{"address", "applet", "area", "article", "aside", "base", "basefont", "bgsound", + // tag searches. must be sorted, used in inSorted. MUST update HtmlTreeBuilderTest if more arrays are added. + static final String[] TagsSearchInScope = new String[]{"applet", "caption", "html", "marquee", "object", "table", "td", "th"}; + static final String[] TagSearchList = new String[]{"ol", "ul"}; + static final String[] TagSearchButton = new String[]{"button"}; + static final String[] TagSearchTableScope = new String[]{"html", "table"}; + static final String[] TagSearchSelectScope = new String[]{"optgroup", "option"}; + static final String[] TagSearchEndTags = new String[]{"dd", "dt", "li", "optgroup", "option", "p", "rp", "rt"}; + static final String[] TagSearchSpecial = new String[]{"address", "applet", "area", "article", "aside", "base", "basefont", "bgsound", "blockquote", "body", "br", "button", "caption", "center", "col", "colgroup", "command", "dd", "details", "dir", "div", "dl", "dt", "embed", "fieldset", "figcaption", "figure", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "iframe", "img", "input", "isindex", "li", "link", "listing", "marquee", "menu", "meta", "nav", "noembed", "noframes", "noscript", "object", "ol", "p", "param", "plaintext", "pre", "script", "section", "select", "style", "summary", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", - "title", "tr", "ul", "wbr", "xmp"}); + "title", "tr", "ul", "wbr", "xmp"}; public static final int MaxScopeSearchDepth = 100; // prevents the parser bogging down in exceptionally broken pages diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 3d869d6d0f..f86f8bcf38 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1504,28 +1504,28 @@ private static void handleRawtext(Token.StartTag startTag, HtmlTreeBuilder tb) { // lists of tags to search through. A little harder to read here, but causes less GC than dynamic varargs. // was contributing around 10% of parse GC load. - // must make sure these are sorted, as used in findSorted - private static final class Constants { - private static final String[] InBodyStartToHead = StringUtil.sort(new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"}); - private static final String[] InBodyStartPClosers = StringUtil.sort(new String[]{"address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", + // must make sure these are sorted, as used in findSorted. MUST update HtmlTreebuilderStateTest if more arrays added. + static final class Constants { + static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"}; + static final String[] InBodyStartPClosers = new String[]{"address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", - "p", "section", "summary", "ul"}); - private static final String[] Headings = StringUtil.sort(new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}); - private static final String[] InBodyStartPreListing = StringUtil.sort(new String[]{"pre", "listing"}); - private static final String[] InBodyStartLiBreakers = StringUtil.sort(new String[]{"address", "div", "p"}); - private static final String[] DdDt = StringUtil.sort(new String[]{"dd", "dt"}); - private static final String[] Formatters = StringUtil.sort(new String[]{"b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u"}); - private static final String[] InBodyStartApplets = StringUtil.sort(new String[]{"applet", "marquee", "object"}); - private static final String[] InBodyStartEmptyFormatters = StringUtil.sort(new String[]{"area", "br", "embed", "img", "keygen", "wbr"}); - private static final String[] InBodyStartMedia = StringUtil.sort(new String[]{"param", "source", "track"}); - private static final String[] InBodyStartInputAttribs = StringUtil.sort(new String[]{"name", "action", "prompt"}); - private static final String[] InBodyStartOptions = StringUtil.sort(new String[]{"optgroup", "option"}); - private static final String[] InBodyStartRuby = StringUtil.sort(new String[]{"rp", "rt"}); - private static final String[] InBodyStartDrop = StringUtil.sort(new String[]{"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"}); - private static final String[] InBodyEndClosers = StringUtil.sort(new String[]{"address", "article", "aside", "blockquote", "button", "center", "details", "dir", "div", + "p", "section", "summary", "ul"}; + static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; + static final String[] InBodyStartPreListing = new String[]{"listing", "pre"}; + static final String[] InBodyStartLiBreakers = new String[]{"address", "div", "p"}; + static final String[] DdDt = new String[]{"dd", "dt"}; + static final String[] Formatters = new String[]{"b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u"}; + static final String[] InBodyStartApplets = new String[]{"applet", "marquee", "object"}; + static final String[] InBodyStartEmptyFormatters = new String[]{"area", "br", "embed", "img", "keygen", "wbr"}; + static final String[] InBodyStartMedia = new String[]{"param", "source", "track"}; + static final String[] InBodyStartInputAttribs = new String[]{"action", "name", "prompt"}; + static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; + static final String[] InBodyStartRuby = new String[]{"rp", "rt"}; + static final String[] InBodyStartDrop = new String[]{"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"}; + static final String[] InBodyEndClosers = new String[]{"address", "article", "aside", "blockquote", "button", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "menu", - "nav", "ol", "pre", "section", "summary", "ul"}); - private static final String[] InBodyEndAdoptionFormatters = StringUtil.sort(new String[]{"a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u"}); - private static final String[] InBodyEndTableFosters = StringUtil.sort(new String[]{"table", "tbody", "tfoot", "thead", "tr"}); + "nav", "ol", "pre", "section", "summary", "ul"}; + static final String[] InBodyEndAdoptionFormatters = new String[]{"a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u"}; + static final String[] InBodyEndTableFosters = new String[]{"table", "tbody", "tfoot", "thead", "tr"}; } } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java new file mode 100644 index 0000000000..80ca688afe --- /dev/null +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -0,0 +1,39 @@ +package org.jsoup.parser; + +import org.jsoup.parser.HtmlTreeBuilderState.Constants; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertArrayEquals; + +public class HtmlTreeBuilderStateTest { + @Test + public void ensureArraysAreSorted() { + String[][] arrays = { + Constants.InBodyStartToHead, + Constants.InBodyStartPClosers, + Constants.Headings, + Constants.InBodyStartPreListing, + Constants.InBodyStartLiBreakers, + Constants.DdDt, + Constants.Formatters, + Constants.InBodyStartApplets, + Constants.InBodyStartEmptyFormatters, + Constants.InBodyStartMedia, + Constants.InBodyStartInputAttribs, + Constants.InBodyStartOptions, + Constants.InBodyStartRuby, + Constants.InBodyStartDrop, + Constants.InBodyEndClosers, + Constants.InBodyEndAdoptionFormatters, + Constants.InBodyEndTableFosters + }; + + for (String[] array : arrays) { + String[] copy = Arrays.copyOf(array, array.length); + Arrays.sort(array); + assertArrayEquals(array, copy); + } + } +} diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java new file mode 100644 index 0000000000..3753376873 --- /dev/null +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java @@ -0,0 +1,28 @@ +package org.jsoup.parser; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertArrayEquals; + +public class HtmlTreeBuilderTest { + @Test + public void ensureSearchArraysAreSorted() { + String[][] arrays = { + HtmlTreeBuilder.TagsSearchInScope, + HtmlTreeBuilder.TagSearchList, + HtmlTreeBuilder.TagSearchButton, + HtmlTreeBuilder.TagSearchTableScope, + HtmlTreeBuilder.TagSearchSelectScope, + HtmlTreeBuilder.TagSearchEndTags, + HtmlTreeBuilder.TagSearchSpecial + }; + + for (String[] array : arrays) { + String[] copy = Arrays.copyOf(array, array.length); + Arrays.sort(array); + assertArrayEquals(array, copy); + } + } +} From 06159687218f6b9acdcab87ac112dc680867aa7f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Oct 2017 15:30:57 -0700 Subject: [PATCH 186/774] Avoid covariant return type change problems If compiled on JDK9, wouldn't run on older JDKs, because of the covariant return type which meant older JDKs couldn't find the right method. --- src/main/java/org/jsoup/helper/DataUtil.java | 10 ++++++---- src/main/java/org/jsoup/helper/HttpConnection.java | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 796c693c2c..3ac0edb066 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -15,6 +15,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; @@ -104,7 +105,7 @@ static Document parseInputStream(InputStream input, String charsetName, String b input.reset(); // look for BOM - overrides any other header or input - BomCharset bomCharset = detectCharsetFromBom(firstBytes, charsetName); + BomCharset bomCharset = detectCharsetFromBom(firstBytes); if (bomCharset != null) { charsetName = bomCharset.charset; input.skip(bomCharset.offset); @@ -231,12 +232,13 @@ static String mimeBoundary() { return mime.toString(); } - private static BomCharset detectCharsetFromBom(final ByteBuffer byteData, final String charsetName) { - byteData.mark(); + private static BomCharset detectCharsetFromBom(final ByteBuffer byteData) { + final Buffer buffer = byteData; // .mark and rewind used to return Buffer, now ByteBuffer, so cast for backward compat + buffer.mark(); byte[] bom = new byte[4]; if (byteData.remaining() >= bom.length) { byteData.get(bom); - byteData.rewind(); + buffer.rewind(); } if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF || // BE bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { // LE diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 4bfb80fe4e..7ee1cbc8db 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -31,6 +31,7 @@ import java.net.URI; import java.net.URL; import java.net.URLEncoder; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; @@ -859,7 +860,7 @@ public String body() { body = Charset.forName(DataUtil.defaultCharset).decode(byteData).toString(); else body = Charset.forName(charset).decode(byteData).toString(); - byteData.rewind(); + ((Buffer)byteData).rewind(); // cast to avoid covariant return type change in jdk9 return body; } From f3035cafbbd8830de75f641920f8e5dbf0070f7c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Oct 2017 17:35:20 -0700 Subject: [PATCH 187/774] Moved entity data from resource to .class Fixes #959 and other issues impacting Android (like crashing on first run after install) --- CHANGES | 4 + src/main/java/org/jsoup/nodes/Entities.java | 99 +- .../java/org/jsoup/nodes/EntitiesData.java | 10 + .../org/jsoup/nodes/entities-base.properties | 106 - .../org/jsoup/nodes/entities-full.properties | 2125 ----------------- .../org/jsoup/nodes/entities-xhtml.properties | 4 - .../java/org/jsoup/nodes/BuildEntities.java | 16 +- 7 files changed, 61 insertions(+), 2303 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/EntitiesData.java delete mode 100644 src/main/java/org/jsoup/nodes/entities-base.properties delete mode 100644 src/main/java/org/jsoup/nodes/entities-full.properties delete mode 100644 src/main/java/org/jsoup/nodes/entities-xhtml.properties diff --git a/CHANGES b/CHANGES index 085782c9c7..1c3a5c4b03 100644 --- a/CHANGES +++ b/CHANGES @@ -77,6 +77,10 @@ jsoup changelog * Improved parse time for pages with exceptionally deeply nested tags. + * Improvement / workaround: modified the Entities implementation to load its data from a .class vs from a jar resource. + Faster, and safer on Android. + + *** Release 1.10.3 [2017-Jun-11] * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values, respectively. This makes it simpler to for example get a list of each URL on a page: diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 2c9795357e..ce98e7f1eb 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -6,11 +6,7 @@ import org.jsoup.parser.CharacterReader; import org.jsoup.parser.Parser; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.Arrays; import java.util.HashMap; @@ -19,15 +15,13 @@ import static org.jsoup.nodes.Entities.EscapeMode.extended; /** - * HTML entities, and escape routines. - * Source: W3C HTML - * named character references. + * HTML entities, and escape routines. Source: W3C + * HTML named character references. */ public class Entities { private static final int empty = -1; private static final String emptyName = ""; static final int codepointRadix = 36; - private static final Charset ASCII = Charset.forName("ascii"); private static final char[] codeDelims = {',', ';'}; private static final HashMap multipoints = new HashMap<>(); // name -> multiple character references private static final Document.OutputSettings DefaultOutput = new Document.OutputSettings(); @@ -36,15 +30,15 @@ public enum EscapeMode { /** * Restricted entities suitable for XHTML output: lt, gt, amp, and quot only. */ - xhtml("entities-xhtml.properties", 4), + xhtml(EntitiesData.xmlPoints, 4), /** * Default HTML output entities. */ - base("entities-base.properties", 106), + base(EntitiesData.basePoints, 106), /** * Complete HTML entities. */ - extended("entities-full.properties", 2125); + extended(EntitiesData.fullPoints, 2125); // table of named references to their codepoints. sorted so we can binary search. built by BuildEntities. private String[] nameKeys; @@ -146,8 +140,8 @@ public static int codepointsForName(final String name, final int[] codepoints) { } /** - * HTML escape an input string. That is, {@code <} is returned as - * {@code <} + * HTML escape an input string. That is, {@code <} is returned as {@code <} + * * @param string the un-escaped string to escape * @param out the output settings to use * @return the escaped string @@ -167,6 +161,7 @@ public static String escape(String string, Document.OutputSettings out) { /** * HTML escape an input string, using the default settings (UTF-8, base entities). That is, {@code <} is returned as * {@code <} + * * @param string the un-escaped string to escape * @return the escaped string */ @@ -260,6 +255,7 @@ private static void appendEncoded(Appendable accum, EscapeMode escapeMode, int c /** * Un-escape an HTML escaped string. That is, {@code <} is returned as {@code <}. + * * @param string the HTML string to un-escape * @return the unescaped string */ @@ -315,64 +311,45 @@ static CoreCharset byName(final String name) { } } - private static void load(EscapeMode e, String file, int size) { + private static void load(EscapeMode e, String pointsData, int size) { e.nameKeys = new String[size]; e.codeVals = new int[size]; e.codeKeys = new int[size]; e.nameVals = new String[size]; - InputStream stream = Entities.class.getResourceAsStream(file); - if (stream == null) - throw new IllegalStateException("Could not read resource " + file + ". Make sure you copy resources for " + Entities.class.getCanonicalName()); - int i = 0; - BufferedReader input = null; - try { - input = new BufferedReader(new InputStreamReader(stream, ASCII)); - CharacterReader reader = new CharacterReader(input); - - while (!reader.isEmpty()) { - // NotNestedLessLess=10913,824;1887 - - final String name = reader.consumeTo('='); - reader.advance(); - final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); - final char codeDelim = reader.current(); - reader.advance(); - final int cp2; - if (codeDelim == ',') { - cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); - reader.advance(); - } else { - cp2 = empty; - } - String indexS = reader.consumeTo('\n'); - // default git checkout on windows will add a \r there, so remove - if (indexS.charAt(indexS.length() - 1) == '\r') { - indexS = indexS.substring(0, indexS.length() - 1); - } - final int index = Integer.parseInt(indexS, codepointRadix); + CharacterReader reader = new CharacterReader(pointsData); + + while (!reader.isEmpty()) { + // NotNestedLessLess=10913,824;1887& + + final String name = reader.consumeTo('='); + reader.advance(); + final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); + final char codeDelim = reader.current(); + reader.advance(); + final int cp2; + if (codeDelim == ',') { + cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); reader.advance(); + } else { + cp2 = empty; + } + final String indexS = reader.consumeTo('&'); + final int index = Integer.parseInt(indexS, codepointRadix); + reader.advance(); - e.nameKeys[i] = name; - e.codeVals[i] = cp1; - e.codeKeys[index] = cp1; - e.nameVals[index] = name; + e.nameKeys[i] = name; + e.codeVals[i] = cp1; + e.codeKeys[index] = cp1; + e.nameVals[index] = name; - if (cp2 != empty) { - multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); - } - i++; - } - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException e1) { - //ignore exception + if (cp2 != empty) { + multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); } + i++; } - Validate.isTrue(i == size, "Unexpected count of entities loaded for " + file); + + Validate.isTrue(i == size, "Unexpected count of entities loaded"); } } diff --git a/src/main/java/org/jsoup/nodes/EntitiesData.java b/src/main/java/org/jsoup/nodes/EntitiesData.java new file mode 100644 index 0000000000..6a04e0a4f1 --- /dev/null +++ b/src/main/java/org/jsoup/nodes/EntitiesData.java @@ -0,0 +1,10 @@ +package org.jsoup.nodes; + +/** + * Holds packed data that represents Entity name=value pairs. Parsed by Entities, created by BuildEntities. + */ +class EntitiesData { + static final String xmlPoints = "amp=12;1>=1q;3<=1o;2"=y;0&"; + static final String basePoints = "AElig=5i;1c&=12;2Á=5d;17Â=5e;18À=5c;16Å=5h;1bÃ=5f;19Ä=5g;1a©=4p;hÇ=5j;1dÐ=5s;1mÉ=5l;1fÊ=5m;1gÈ=5k;1eË=5n;1h>=1q;6Í=5p;1jÎ=5q;1kÌ=5o;1iÏ=5r;1l<=1o;4Ñ=5t;1nÓ=5v;1pÔ=5w;1qÒ=5u;1oØ=60;1uÕ=5x;1rÖ=5y;1s"=y;0®=4u;nÞ=66;20Ú=62;1wÛ=63;1xÙ=61;1vÜ=64;1yÝ=65;1zá=69;23â=6a;24´=50;uæ=6e;28à=68;22&=12;3å=6d;27ã=6b;25ä=6c;26¦=4m;eç=6f;29¸=54;y¢=4i;a©=4p;i¤=4k;c°=4w;q÷=6v;2pé=6h;2bê=6i;2cè=6g;2að=6o;2ië=6j;2d½=59;13¼=58;12¾=5a;14>=1q;7í=6l;2fî=6m;2g¡=4h;9ì=6k;2e¿=5b;15ï=6n;2h«=4r;k<=1o;5¯=4v;pµ=51;v·=53;x =4g;8¬=4s;lñ=6p;2jó=6r;2lô=6s;2mò=6q;2kª=4q;jº=56;10ø=6w;2qõ=6t;2nö=6u;2o¶=52;w±=4x;r£=4j;b"=y;1»=57;11®=4u;o§=4n;f­=4t;m¹=55;z²=4y;s³=4z;tß=67;21þ=72;2w×=5z;1tú=6y;2sû=6z;2tù=6x;2r¨=4o;gü=70;2uý=71;2v¥=4l;dÿ=73;2x&"; + static final String fullPoints = "AElig=5i;2v&=12;8Á=5d;2p&Abreve=76;4kÂ=5e;2q&Acy=sw;av&Afr=2kn8;1khÀ=5c;2o&Alpha=pd;8d&Amacr=74;4i&And=8cz;1e1&Aogon=78;4m&Aopf=2koo;1ls&ApplyFunction=6e9;ewÅ=5h;2t&Ascr=2kkc;1jc&Assign=6s4;s6Ã=5f;2rÄ=5g;2s&Backslash=6qe;o1&Barv=8h3;1it&Barwed=6x2;120&Bcy=sx;aw&Because=6r9;pw&Bernoullis=6jw;gn&Beta=pe;8e&Bfr=2kn9;1ki&Bopf=2kop;1lt&Breve=k8;82&Bscr=6jw;gp&Bumpeq=6ry;ro&CHcy=tj;bi©=4p;1q&Cacute=7a;4o&Cap=6vm;zz&CapitalDifferentialD=6kl;h8&Cayleys=6jx;gq&Ccaron=7g;4uÇ=5j;2w&Ccirc=7c;4q&Cconint=6r4;pn&Cdot=7e;4s&Cedilla=54;2e&CenterDot=53;2b&Cfr=6jx;gr&Chi=pz;8y&CircleDot=6u1;x8&CircleMinus=6ty;x3&CirclePlus=6tx;x1&CircleTimes=6tz;x5&ClockwiseContourIntegral=6r6;pp&CloseCurlyDoubleQuote=6cd;e0&CloseCurlyQuote=6c9;dt&Colon=6rb;q1&Colone=8dw;1en&Congruent=6sh;sn&Conint=6r3;pm&ContourIntegral=6r2;pi&Copf=6iq;f7&Coproduct=6q8;nq&CounterClockwiseContourIntegral=6r7;pr&Cross=8bz;1d8&Cscr=2kke;1jd&Cup=6vn;100&CupCap=6rx;rk&DD=6kl;h9&DDotrahd=841;184&DJcy=si;ai&DScy=sl;al&DZcy=sv;au&Dagger=6ch;e7&Darr=6n5;j5&Dashv=8h0;1ir&Dcaron=7i;4w&Dcy=t0;az&Del=6pz;n9&Delta=pg;8g&Dfr=2knb;1kj&DiacriticalAcute=50;27&DiacriticalDot=k9;84&DiacriticalDoubleAcute=kd;8a&DiacriticalGrave=2o;13&DiacriticalTilde=kc;88&Diamond=6v8;za&DifferentialD=6km;ha&Dopf=2kor;1lu&Dot=4o;1n&DotDot=6ho;f5&DotEqual=6s0;rw&DoubleContourIntegral=6r3;pl&DoubleDot=4o;1m&DoubleDownArrow=6oj;m0&DoubleLeftArrow=6og;lq&DoubleLeftRightArrow=6ok;m3&DoubleLeftTee=8h0;1iq&DoubleLongLeftArrow=7w8;17g&DoubleLongLeftRightArrow=7wa;17m&DoubleLongRightArrow=7w9;17j&DoubleRightArrow=6oi;lw&DoubleRightTee=6ug;xz&DoubleUpArrow=6oh;lt&DoubleUpDownArrow=6ol;m7&DoubleVerticalBar=6qt;ov&DownArrow=6mr;i8&DownArrowBar=843;186&DownArrowUpArrow=6ph;mn&DownBreve=lt;8c&DownLeftRightVector=85s;198&DownLeftTeeVector=866;19m&DownLeftVector=6nx;ke&DownLeftVectorBar=85y;19e&DownRightTeeVector=867;19n&DownRightVector=6o1;kq&DownRightVectorBar=85z;19f&DownTee=6uc;xs&DownTeeArrow=6nb;jh&Downarrow=6oj;m1&Dscr=2kkf;1je&Dstrok=7k;4y&ENG=96;6gÐ=5s;35É=5l;2y&Ecaron=7u;56Ê=5m;2z&Ecy=tp;bo&Edot=7q;52&Efr=2knc;1kkÈ=5k;2x&Element=6q0;na&Emacr=7m;50&EmptySmallSquare=7i3;15x&EmptyVerySmallSquare=7fv;150&Eogon=7s;54&Eopf=2kos;1lv&Epsilon=ph;8h&Equal=8dx;1eo&EqualTilde=6rm;qp&Equilibrium=6oc;li&Escr=6k0;gu&Esim=8dv;1em&Eta=pj;8jË=5n;30&Exists=6pv;mz&ExponentialE=6kn;hc&Fcy=tg;bf&Ffr=2knd;1kl&FilledSmallSquare=7i4;15y&FilledVerySmallSquare=7fu;14w&Fopf=2kot;1lw&ForAll=6ps;ms&Fouriertrf=6k1;gv&Fscr=6k1;gw&GJcy=sj;aj>=1q;r&Gamma=pf;8f&Gammad=rg;a5&Gbreve=7y;5a&Gcedil=82;5e&Gcirc=7w;58&Gcy=sz;ay&Gdot=80;5c&Gfr=2kne;1km&Gg=6vt;10c&Gopf=2kou;1lx&GreaterEqual=6sl;sv&GreaterEqualLess=6vv;10i&GreaterFullEqual=6sn;t6&GreaterGreater=8f6;1gh&GreaterLess=6t3;ul&GreaterSlantEqual=8e6;1f5&GreaterTilde=6sz;ub&Gscr=2kki;1jf&Gt=6sr;tr&HARDcy=tm;bl&Hacek=jr;80&Hat=2m;10&Hcirc=84;5f&Hfr=6j0;fe&HilbertSpace=6iz;fa&Hopf=6j1;fg&HorizontalLine=7b4;13i&Hscr=6iz;fc&Hstrok=86;5h&HumpDownHump=6ry;rn&HumpEqual=6rz;rs&IEcy=t1;b0&IJlig=8i;5s&IOcy=sh;ahÍ=5p;32Î=5q;33&Icy=t4;b3&Idot=8g;5p&Ifr=6j5;fqÌ=5o;31&Im=6j5;fr&Imacr=8a;5l&ImaginaryI=6ko;hf&Implies=6oi;ly&Int=6r0;pf&Integral=6qz;pd&Intersection=6v6;z4&InvisibleComma=6eb;f0&InvisibleTimes=6ea;ey&Iogon=8e;5n&Iopf=2kow;1ly&Iota=pl;8l&Iscr=6j4;fn&Itilde=88;5j&Iukcy=sm;amÏ=5r;34&Jcirc=8k;5u&Jcy=t5;b4&Jfr=2knh;1kn&Jopf=2kox;1lz&Jscr=2kkl;1jg&Jsercy=so;ao&Jukcy=sk;ak&KHcy=th;bg&KJcy=ss;as&Kappa=pm;8m&Kcedil=8m;5w&Kcy=t6;b5&Kfr=2kni;1ko&Kopf=2koy;1m0&Kscr=2kkm;1jh&LJcy=sp;ap<=1o;m&Lacute=8p;5z&Lambda=pn;8n&Lang=7vu;173&Laplacetrf=6j6;fs&Larr=6n2;j1&Lcaron=8t;63&Lcedil=8r;61&Lcy=t7;b6&LeftAngleBracket=7vs;16x&LeftArrow=6mo;hu&LeftArrowBar=6p0;mj&LeftArrowRightArrow=6o6;l3&LeftCeiling=6x4;121&LeftDoubleBracket=7vq;16t&LeftDownTeeVector=869;19p&LeftDownVector=6o3;kw&LeftDownVectorBar=861;19h&LeftFloor=6x6;125&LeftRightArrow=6ms;ib&LeftRightVector=85q;196&LeftTee=6ub;xq&LeftTeeArrow=6n8;ja&LeftTeeVector=862;19i&LeftTriangle=6uq;ya&LeftTriangleBar=89b;1c0&LeftTriangleEqual=6us;yg&LeftUpDownVector=85t;199&LeftUpTeeVector=868;19o&LeftUpVector=6nz;kk&LeftUpVectorBar=860;19g&LeftVector=6nw;kb&LeftVectorBar=85u;19a&Leftarrow=6og;lr&Leftrightarrow=6ok;m4&LessEqualGreater=6vu;10e&LessFullEqual=6sm;t0&LessGreater=6t2;ui&LessLess=8f5;1gf&LessSlantEqual=8e5;1ez&LessTilde=6sy;u8&Lfr=2knj;1kp&Ll=6vs;109&Lleftarrow=6oq;me&Lmidot=8v;65&LongLeftArrow=7w5;177&LongLeftRightArrow=7w7;17d&LongRightArrow=7w6;17a&Longleftarrow=7w8;17h&Longleftrightarrow=7wa;17n&Longrightarrow=7w9;17k&Lopf=2koz;1m1&LowerLeftArrow=6mx;iq&LowerRightArrow=6mw;in&Lscr=6j6;fu&Lsh=6nk;jv&Lstrok=8x;67&Lt=6sq;tl&Map=83p;17v&Mcy=t8;b7&MediumSpace=6e7;eu&Mellintrf=6k3;gx&Mfr=2knk;1kq&MinusPlus=6qb;nv&Mopf=2kp0;1m2&Mscr=6k3;gz&Mu=po;8o&NJcy=sq;aq&Nacute=8z;69&Ncaron=93;6d&Ncedil=91;6b&Ncy=t9;b8&NegativeMediumSpace=6bv;dc&NegativeThickSpace=6bv;dd&NegativeThinSpace=6bv;de&NegativeVeryThinSpace=6bv;db&NestedGreaterGreater=6sr;tq&NestedLessLess=6sq;tk&NewLine=a;1&Nfr=2knl;1kr&NoBreak=6e8;ev&NonBreakingSpace=4g;1d&Nopf=6j9;fx&Not=8h8;1ix&NotCongruent=6si;sp&NotCupCap=6st;tv&NotDoubleVerticalBar=6qu;p0&NotElement=6q1;ne&NotEqual=6sg;sk&NotEqualTilde=6rm,mw;qn&NotExists=6pw;n1&NotGreater=6sv;tz&NotGreaterEqual=6sx;u5&NotGreaterFullEqual=6sn,mw;t3&NotGreaterGreater=6sr,mw;tn&NotGreaterLess=6t5;uq&NotGreaterSlantEqual=8e6,mw;1f2&NotGreaterTilde=6t1;ug&NotHumpDownHump=6ry,mw;rl&NotHumpEqual=6rz,mw;rq&NotLeftTriangle=6wa;113&NotLeftTriangleBar=89b,mw;1bz&NotLeftTriangleEqual=6wc;119&NotLess=6su;tw&NotLessEqual=6sw;u2&NotLessGreater=6t4;uo&NotLessLess=6sq,mw;th&NotLessSlantEqual=8e5,mw;1ew&NotLessTilde=6t0;ue&NotNestedGreaterGreater=8f6,mw;1gg&NotNestedLessLess=8f5,mw;1ge&NotPrecedes=6tc;vb&NotPrecedesEqual=8fj,mw;1gv&NotPrecedesSlantEqual=6w0;10p&NotReverseElement=6q4;nl&NotRightTriangle=6wb;116&NotRightTriangleBar=89c,mw;1c1&NotRightTriangleEqual=6wd;11c&NotSquareSubset=6tr,mw;wh&NotSquareSubsetEqual=6w2;10t&NotSquareSuperset=6ts,mw;wl&NotSquareSupersetEqual=6w3;10v&NotSubset=6te,6he;vh&NotSubsetEqual=6tk;w0&NotSucceeds=6td;ve&NotSucceedsEqual=8fk,mw;1h1&NotSucceedsSlantEqual=6w1;10r&NotSucceedsTilde=6tb,mw;v7&NotSuperset=6tf,6he;vm&NotSupersetEqual=6tl;w3&NotTilde=6rl;ql&NotTildeEqual=6ro;qv&NotTildeFullEqual=6rr;r1&NotTildeTilde=6rt;r9&NotVerticalBar=6qs;or&Nscr=2kkp;1jiÑ=5t;36&Nu=pp;8p&OElig=9e;6mÓ=5v;38Ô=5w;39&Ocy=ta;b9&Odblac=9c;6k&Ofr=2knm;1ksÒ=5u;37&Omacr=98;6i&Omega=q1;90&Omicron=pr;8r&Oopf=2kp2;1m3&OpenCurlyDoubleQuote=6cc;dy&OpenCurlyQuote=6c8;dr&Or=8d0;1e2&Oscr=2kkq;1jjØ=60;3dÕ=5x;3a&Otimes=8c7;1dfÖ=5y;3b&OverBar=6da;em&OverBrace=732;13b&OverBracket=71w;134&OverParenthesis=730;139&PartialD=6pu;mx&Pcy=tb;ba&Pfr=2knn;1kt&Phi=py;8x&Pi=ps;8s&PlusMinus=4x;22&Poincareplane=6j0;fd&Popf=6jd;g3&Pr=8fv;1hl&Precedes=6t6;us&PrecedesEqual=8fj;1gy&PrecedesSlantEqual=6t8;uy&PrecedesTilde=6ta;v4&Prime=6cz;eg&Product=6q7;no&Proportion=6rb;q0&Proportional=6ql;oa&Pscr=2kkr;1jk&Psi=q0;8z"=y;3&Qfr=2kno;1ku&Qopf=6je;g5&Qscr=2kks;1jl&RBarr=840;183®=4u;1x&Racute=9g;6o&Rang=7vv;174&Rarr=6n4;j4&Rarrtl=846;187&Rcaron=9k;6s&Rcedil=9i;6q&Rcy=tc;bb&Re=6jg;gb&ReverseElement=6q3;nh&ReverseEquilibrium=6ob;le&ReverseUpEquilibrium=86n;1a4&Rfr=6jg;ga&Rho=pt;8t&RightAngleBracket=7vt;170&RightArrow=6mq;i3&RightArrowBar=6p1;ml&RightArrowLeftArrow=6o4;ky&RightCeiling=6x5;123&RightDoubleBracket=7vr;16v&RightDownTeeVector=865;19l&RightDownVector=6o2;kt&RightDownVectorBar=85x;19d&RightFloor=6x7;127&RightTee=6ua;xo&RightTeeArrow=6na;je&RightTeeVector=863;19j&RightTriangle=6ur;yd&RightTriangleBar=89c;1c2&RightTriangleEqual=6ut;yk&RightUpDownVector=85r;197&RightUpTeeVector=864;19k&RightUpVector=6ny;kh&RightUpVectorBar=85w;19c&RightVector=6o0;kn&RightVectorBar=85v;19b&Rightarrow=6oi;lx&Ropf=6jh;gd&RoundImplies=86o;1a6&Rrightarrow=6or;mg&Rscr=6jf;g7&Rsh=6nl;jx&RuleDelayed=8ac;1cb&SHCHcy=tl;bk&SHcy=tk;bj&SOFTcy=to;bn&Sacute=9m;6u&Sc=8fw;1hm&Scaron=9s;70&Scedil=9q;6y&Scirc=9o;6w&Scy=td;bc&Sfr=2knq;1kv&ShortDownArrow=6mr;i7&ShortLeftArrow=6mo;ht&ShortRightArrow=6mq;i2&ShortUpArrow=6mp;hy&Sigma=pv;8u&SmallCircle=6qg;o6&Sopf=2kp6;1m4&Sqrt=6qi;o9&Square=7fl;14t&SquareIntersection=6tv;ww&SquareSubset=6tr;wi&SquareSubsetEqual=6tt;wp&SquareSuperset=6ts;wm&SquareSupersetEqual=6tu;ws&SquareUnion=6tw;wz&Sscr=2kku;1jm&Star=6va;zf&Sub=6vk;zw&Subset=6vk;zv&SubsetEqual=6ti;vu&Succeeds=6t7;uv&SucceedsEqual=8fk;1h4&SucceedsSlantEqual=6t9;v1&SucceedsTilde=6tb;v8&SuchThat=6q3;ni&Sum=6q9;ns&Sup=6vl;zy&Superset=6tf;vp&SupersetEqual=6tj;vx&Supset=6vl;zxÞ=66;3j&TRADE=6jm;gf&TSHcy=sr;ar&TScy=ti;bh&Tab=9;0&Tau=pw;8v&Tcaron=9w;74&Tcedil=9u;72&Tcy=te;bd&Tfr=2knr;1kw&Therefore=6r8;pt&Theta=pk;8k&ThickSpace=6e7,6bu;et&ThinSpace=6bt;d7&Tilde=6rg;q9&TildeEqual=6rn;qs&TildeFullEqual=6rp;qy&TildeTilde=6rs;r4&Topf=2kp7;1m5&TripleDot=6hn;f3&Tscr=2kkv;1jn&Tstrok=9y;76Ú=62;3f&Uarr=6n3;j2&Uarrocir=85l;193&Ubrcy=su;at&Ubreve=a4;7cÛ=63;3g&Ucy=tf;be&Udblac=a8;7g&Ufr=2kns;1kxÙ=61;3e&Umacr=a2;7a&UnderBar=2n;11&UnderBrace=733;13c&UnderBracket=71x;136&UnderParenthesis=731;13a&Union=6v7;z8&UnionPlus=6tq;wf&Uogon=aa;7i&Uopf=2kp8;1m6&UpArrow=6mp;hz&UpArrowBar=842;185&UpArrowDownArrow=6o5;l1&UpDownArrow=6mt;ie&UpEquilibrium=86m;1a2&UpTee=6ud;xv&UpTeeArrow=6n9;jc&Uparrow=6oh;lu&Updownarrow=6ol;m8&UpperLeftArrow=6mu;ih&UpperRightArrow=6mv;ik&Upsi=r6;9z&Upsilon=px;8w&Uring=a6;7e&Uscr=2kkw;1jo&Utilde=a0;78Ü=64;3h&VDash=6uj;y3&Vbar=8h7;1iw&Vcy=sy;ax&Vdash=6uh;y1&Vdashl=8h2;1is&Vee=6v5;z3&Verbar=6c6;dp&Vert=6c6;dq&VerticalBar=6qr;on&VerticalLine=3g;18&VerticalSeparator=7rs;16o&VerticalTilde=6rk;qi&VeryThinSpace=6bu;d9&Vfr=2knt;1ky&Vopf=2kp9;1m7&Vscr=2kkx;1jp&Vvdash=6ui;y2&Wcirc=ac;7k&Wedge=6v4;z0&Wfr=2knu;1kz&Wopf=2kpa;1m8&Wscr=2kky;1jq&Xfr=2knv;1l0&Xi=pq;8q&Xopf=2kpb;1m9&Xscr=2kkz;1jr&YAcy=tr;bq&YIcy=sn;an&YUcy=tq;bpÝ=65;3i&Ycirc=ae;7m&Ycy=tn;bm&Yfr=2knw;1l1&Yopf=2kpc;1ma&Yscr=2kl0;1js&Yuml=ag;7o&ZHcy=t2;b1&Zacute=ah;7p&Zcaron=al;7t&Zcy=t3;b2&Zdot=aj;7r&ZeroWidthSpace=6bv;df&Zeta=pi;8i&Zfr=6js;gl&Zopf=6jo;gi&Zscr=2kl1;1jtá=69;3m&abreve=77;4l&ac=6ri;qg&acE=6ri,mr;qe&acd=6rj;qhâ=6a;3n´=50;28&acy=ts;bræ=6e;3r&af=6e9;ex&afr=2kny;1l2à=68;3l&alefsym=6k5;h3&aleph=6k5;h4&alpha=q9;92&amacr=75;4j&amalg=8cf;1dm&=12;9&and=6qv;p6&andand=8d1;1e3&andd=8d8;1e9&andslope=8d4;1e6&andv=8d6;1e7&ang=6qo;oj&ange=884;1b1&angle=6qo;oi&angmsd=6qp;ol&angmsdaa=888;1b5&angmsdab=889;1b6&angmsdac=88a;1b7&angmsdad=88b;1b8&angmsdae=88c;1b9&angmsdaf=88d;1ba&angmsdag=88e;1bb&angmsdah=88f;1bc&angrt=6qn;og&angrtvb=6v2;yw&angrtvbd=87x;1b0&angsph=6qq;om&angst=5h;2u&angzarr=70c;12z&aogon=79;4n&aopf=2kpe;1mb&ap=6rs;r8&apE=8ds;1ej&apacir=8dr;1eh&ape=6ru;rd&apid=6rv;rf&apos=13;a&approx=6rs;r5&approxeq=6ru;rcå=6d;3q&ascr=2kl2;1ju&ast=16;e&asymp=6rs;r6&asympeq=6rx;rjã=6b;3oä=6c;3p&awconint=6r7;ps&awint=8b5;1cr&bNot=8h9;1iy&backcong=6rw;rg&backepsilon=s6;af&backprime=6d1;ei&backsim=6rh;qc&backsimeq=6vh;zp&barvee=6v1;yv&barwed=6x1;11y&barwedge=6x1;11x&bbrk=71x;137&bbrktbrk=71y;138&bcong=6rw;rh&bcy=tt;bs&bdquo=6ce;e4&becaus=6r9;py&because=6r9;px&bemptyv=88g;1bd&bepsi=s6;ag&bernou=6jw;go&beta=qa;93&beth=6k6;h5&between=6ss;tt&bfr=2knz;1l3&bigcap=6v6;z5&bigcirc=7hr;15s&bigcup=6v7;z7&bigodot=8ao;1cd&bigoplus=8ap;1cf&bigotimes=8aq;1ch&bigsqcup=8au;1cl&bigstar=7id;15z&bigtriangledown=7gd;15e&bigtriangleup=7g3;154&biguplus=8as;1cj&bigvee=6v5;z1&bigwedge=6v4;yy&bkarow=83x;17x&blacklozenge=8a3;1c9&blacksquare=7fu;14x&blacktriangle=7g4;156&blacktriangledown=7ge;15g&blacktriangleleft=7gi;15k&blacktriangleright=7g8;15a&blank=74z;13f&blk12=7f6;14r&blk14=7f5;14q&blk34=7f7;14s&block=7ew;14p&bne=1p,6hx;o&bnequiv=6sh,6hx;sm&bnot=6xc;12d&bopf=2kpf;1mc&bot=6ud;xx&bottom=6ud;xu&bowtie=6vc;zi&boxDL=7dj;141&boxDR=7dg;13y&boxDl=7di;140&boxDr=7df;13x&boxH=7dc;13u&boxHD=7dy;14g&boxHU=7e1;14j&boxHd=7dw;14e&boxHu=7dz;14h&boxUL=7dp;147&boxUR=7dm;144&boxUl=7do;146&boxUr=7dl;143&boxV=7dd;13v&boxVH=7e4;14m&boxVL=7dv;14d&boxVR=7ds;14a&boxVh=7e3;14l&boxVl=7du;14c&boxVr=7dr;149&boxbox=895;1bw&boxdL=7dh;13z&boxdR=7de;13w&boxdl=7bk;13m&boxdr=7bg;13l&boxh=7b4;13j&boxhD=7dx;14f&boxhU=7e0;14i&boxhd=7cc;13r&boxhu=7ck;13s&boxminus=6u7;xi&boxplus=6u6;xg&boxtimes=6u8;xk&boxuL=7dn;145&boxuR=7dk;142&boxul=7bs;13o&boxur=7bo;13n&boxv=7b6;13k&boxvH=7e2;14k&boxvL=7dt;14b&boxvR=7dq;148&boxvh=7cs;13t&boxvl=7c4;13q&boxvr=7bw;13p&bprime=6d1;ej&breve=k8;83¦=4m;1k&bscr=2kl3;1jv&bsemi=6dr;er&bsim=6rh;qd&bsime=6vh;zq&bsol=2k;x&bsolb=891;1bv&bsolhsub=7uw;16r&bull=6ci;e9&bullet=6ci;e8&bump=6ry;rp&bumpE=8fi;1gu&bumpe=6rz;ru&bumpeq=6rz;rt&cacute=7b;4p&cap=6qx;pa&capand=8ck;1dq&capbrcup=8cp;1dv&capcap=8cr;1dx&capcup=8cn;1dt&capdot=8cg;1dn&caps=6qx,1e68;p9&caret=6dd;eo&caron=jr;81&ccaps=8ct;1dz&ccaron=7h;4vç=6f;3s&ccirc=7d;4r&ccups=8cs;1dy&ccupssm=8cw;1e0&cdot=7f;4t¸=54;2f&cemptyv=88i;1bf¢=4i;1g¢erdot=53;2c&cfr=2ko0;1l4&chcy=uf;ce&check=7pv;16j&checkmark=7pv;16i&chi=qv;9s&cir=7gr;15q&cirE=88z;1bt&circ=jq;7z&circeq=6s7;sc&circlearrowleft=6nu;k6&circlearrowright=6nv;k8&circledR=4u;1w&circledS=79k;13g&circledast=6u3;xc&circledcirc=6u2;xa&circleddash=6u5;xe&cire=6s7;sd&cirfnint=8b4;1cq&cirmid=8hb;1j0&cirscir=88y;1bs&clubs=7kz;168&clubsuit=7kz;167&colon=1m;j&colone=6s4;s7&coloneq=6s4;s5&comma=18;g&commat=1s;u&comp=6pt;mv&compfn=6qg;o7&complement=6pt;mu&complexes=6iq;f6&cong=6rp;qz&congdot=8dp;1ef&conint=6r2;pj&copf=2kpg;1md&coprod=6q8;nr©=4p;1r©sr=6jb;fz&crarr=6np;k1&cross=7pz;16k&cscr=2kl4;1jw&csub=8gf;1id&csube=8gh;1if&csup=8gg;1ie&csupe=8gi;1ig&ctdot=6wf;11g&cudarrl=854;18x&cudarrr=851;18u&cuepr=6vy;10m&cuesc=6vz;10o&cularr=6nq;k3&cularrp=859;190&cup=6qy;pc&cupbrcap=8co;1du&cupcap=8cm;1ds&cupcup=8cq;1dw&cupdot=6tp;we&cupor=8cl;1dr&cups=6qy,1e68;pb&curarr=6nr;k5&curarrm=858;18z&curlyeqprec=6vy;10l&curlyeqsucc=6vz;10n&curlyvee=6vi;zr&curlywedge=6vj;zt¤=4k;1i&curvearrowleft=6nq;k2&curvearrowright=6nr;k4&cuvee=6vi;zs&cuwed=6vj;zu&cwconint=6r6;pq&cwint=6r5;po&cylcty=6y5;12u&dArr=6oj;m2&dHar=86d;19t&dagger=6cg;e5&daleth=6k8;h7&darr=6mr;ia&dash=6c0;dl&dashv=6ub;xr&dbkarow=83z;180&dblac=kd;8b&dcaron=7j;4x&dcy=tw;bv&dd=6km;hb&ddagger=6ch;e6&ddarr=6oa;ld&ddotseq=8dz;1ep°=4w;21&delta=qc;95&demptyv=88h;1be&dfisht=873;1aj&dfr=2ko1;1l5&dharl=6o3;kx&dharr=6o2;ku&diam=6v8;zc&diamond=6v8;zb&diamondsuit=7l2;16b&diams=7l2;16c&die=4o;1o&digamma=rh;a6&disin=6wi;11j&div=6v;49÷=6v;48÷ontimes=6vb;zg&divonx=6vb;zh&djcy=uq;co&dlcorn=6xq;12n&dlcrop=6x9;12a&dollar=10;6&dopf=2kph;1me&dot=k9;85&doteq=6s0;rx&doteqdot=6s1;rz&dotminus=6rc;q2&dotplus=6qc;ny&dotsquare=6u9;xm&doublebarwedge=6x2;11z&downarrow=6mr;i9&downdownarrows=6oa;lc&downharpoonleft=6o3;kv&downharpoonright=6o2;ks&drbkarow=840;182&drcorn=6xr;12p&drcrop=6x8;129&dscr=2kl5;1jx&dscy=ut;cr&dsol=8ae;1cc&dstrok=7l;4z&dtdot=6wh;11i&dtri=7gf;15j&dtrif=7ge;15h&duarr=6ph;mo&duhar=86n;1a5&dwangle=886;1b3&dzcy=v3;d0&dzigrarr=7wf;17r&eDDot=8dz;1eq&eDot=6s1;s0é=6h;3u&easter=8dq;1eg&ecaron=7v;57&ecir=6s6;sbê=6i;3v&ecolon=6s5;s9&ecy=ul;ck&edot=7r;53&ee=6kn;he&efDot=6s2;s2&efr=2ko2;1l6&eg=8ey;1g9è=6g;3t&egs=8eu;1g5&egsdot=8ew;1g7&el=8ex;1g8&elinters=73b;13e&ell=6j7;fv&els=8et;1g3&elsdot=8ev;1g6&emacr=7n;51&empty=6px;n7&emptyset=6px;n5&emptyv=6px;n6&emsp=6bn;d2&emsp13=6bo;d3&emsp14=6bp;d4&eng=97;6h&ensp=6bm;d1&eogon=7t;55&eopf=2kpi;1mf&epar=6vp;103&eparsl=89v;1c6&eplus=8dt;1ek&epsi=qd;97&epsilon=qd;96&epsiv=s5;ae&eqcirc=6s6;sa&eqcolon=6s5;s8&eqsim=6rm;qq&eqslantgtr=8eu;1g4&eqslantless=8et;1g2&equals=1p;p&equest=6sf;sj&equiv=6sh;so&equivDD=8e0;1er&eqvparsl=89x;1c8&erDot=6s3;s4&erarr=86p;1a7&escr=6jz;gs&esdot=6s0;ry&esim=6rm;qr&eta=qf;99ð=6o;41ë=6j;3w&euro=6gc;f2&excl=x;2&exist=6pv;n0&expectation=6k0;gt&exponentiale=6kn;hd&fallingdotseq=6s2;s1&fcy=uc;cb&female=7k0;163&ffilig=1dkz;1ja&fflig=1dkw;1j7&ffllig=1dl0;1jb&ffr=2ko3;1l7&filig=1dkx;1j8&fjlig=2u,2y;15&flat=7l9;16e&fllig=1dky;1j9&fltns=7g1;153&fnof=b6;7v&fopf=2kpj;1mg&forall=6ps;mt&fork=6vo;102&forkv=8gp;1in&fpartint=8b1;1cp½=59;2k&frac13=6kz;hh¼=58;2j&frac15=6l1;hj&frac16=6l5;hn&frac18=6l7;hp&frac23=6l0;hi&frac25=6l2;hk¾=5a;2m&frac35=6l3;hl&frac38=6l8;hq&frac45=6l4;hm&frac56=6l6;ho&frac58=6l9;hr&frac78=6la;hs&frasl=6dg;eq&frown=6xu;12r&fscr=2kl7;1jy&gE=6sn;t8&gEl=8ek;1ft&gacute=dx;7x&gamma=qb;94&gammad=rh;a7&gap=8ee;1fh&gbreve=7z;5b&gcirc=7x;59&gcy=tv;bu&gdot=81;5d&ge=6sl;sx&gel=6vv;10k&geq=6sl;sw&geqq=6sn;t7&geqslant=8e6;1f6&ges=8e6;1f7&gescc=8fd;1gn&gesdot=8e8;1f9&gesdoto=8ea;1fb&gesdotol=8ec;1fd&gesl=6vv,1e68;10h&gesles=8es;1g1&gfr=2ko4;1l8&gg=6sr;ts&ggg=6vt;10b&gimel=6k7;h6&gjcy=ur;cp&gl=6t3;un&glE=8eq;1fz&gla=8f9;1gj&glj=8f8;1gi&gnE=6sp;tg&gnap=8ei;1fp&gnapprox=8ei;1fo&gne=8eg;1fl&gneq=8eg;1fk&gneqq=6sp;tf&gnsim=6w7;10y&gopf=2kpk;1mh&grave=2o;14&gscr=6iy;f9&gsim=6sz;ud&gsime=8em;1fv&gsiml=8eo;1fx>=1q;s>cc=8fb;1gl>cir=8e2;1et>dot=6vr;107>lPar=87p;1aw>quest=8e4;1ev>rapprox=8ee;1fg>rarr=86w;1ad>rdot=6vr;106>reqless=6vv;10j>reqqless=8ek;1fs>rless=6t3;um>rsim=6sz;uc&gvertneqq=6sp,1e68;td&gvnE=6sp,1e68;te&hArr=6ok;m5&hairsp=6bu;da&half=59;2l&hamilt=6iz;fb&hardcy=ui;ch&harr=6ms;id&harrcir=85k;192&harrw=6nh;js&hbar=6j3;fl&hcirc=85;5g&hearts=7l1;16a&heartsuit=7l1;169&hellip=6cm;eb&hercon=6ux;yr&hfr=2ko5;1l9&hksearow=84l;18i&hkswarow=84m;18k&hoarr=6pr;mr&homtht=6rf;q5&hookleftarrow=6nd;jj&hookrightarrow=6ne;jl&hopf=2kpl;1mi&horbar=6c5;do&hscr=2kl9;1jz&hslash=6j3;fi&hstrok=87;5i&hybull=6df;ep&hyphen=6c0;dkí=6l;3y&ic=6eb;f1î=6m;3z&icy=u0;bz&iecy=tx;bw¡=4h;1f&iff=6ok;m6&ifr=2ko6;1laì=6k;3x&ii=6ko;hg&iiiint=8b0;1cn&iiint=6r1;pg&iinfin=89o;1c3&iiota=6jt;gm&ijlig=8j;5t&imacr=8b;5m&image=6j5;fp&imagline=6j4;fm&imagpart=6j5;fo&imath=8h;5r&imof=6uv;yo&imped=c5;7w&in=6q0;nd&incare=6it;f8&infin=6qm;of&infintie=89p;1c4&inodot=8h;5q&int=6qz;pe&intcal=6uy;yt&integers=6jo;gh&intercal=6uy;ys&intlarhk=8bb;1cx&intprod=8cc;1dk&iocy=up;cn&iogon=8f;5o&iopf=2kpm;1mj&iota=qh;9b&iprod=8cc;1dl¿=5b;2n&iscr=2kla;1k0&isin=6q0;nc&isinE=6wp;11r&isindot=6wl;11n&isins=6wk;11l&isinsv=6wj;11k&isinv=6q0;nb&it=6ea;ez&itilde=89;5k&iukcy=uu;csï=6n;40&jcirc=8l;5v&jcy=u1;c0&jfr=2ko7;1lb&jmath=fr;7y&jopf=2kpn;1mk&jscr=2klb;1k1&jsercy=uw;cu&jukcy=us;cq&kappa=qi;9c&kappav=s0;a9&kcedil=8n;5x&kcy=u2;c1&kfr=2ko8;1lc&kgreen=8o;5y&khcy=ud;cc&kjcy=v0;cy&kopf=2kpo;1ml&kscr=2klc;1k2&lAarr=6oq;mf&lArr=6og;ls&lAtail=84b;18a&lBarr=83y;17z&lE=6sm;t2&lEg=8ej;1fr&lHar=86a;19q&lacute=8q;60&laemptyv=88k;1bh&lagran=6j6;ft&lambda=qj;9d&lang=7vs;16z&langd=87l;1as&langle=7vs;16y&lap=8ed;1ff«=4r;1t&larr=6mo;hx&larrb=6p0;mk&larrbfs=84f;18e&larrfs=84d;18c&larrhk=6nd;jk&larrlp=6nf;jo&larrpl=855;18y&larrsim=86r;1a9&larrtl=6n6;j7&lat=8ff;1gp&latail=849;188&late=8fh;1gt&lates=8fh,1e68;1gs&lbarr=83w;17w&lbbrk=7si;16p&lbrace=3f;16&lbrack=2j;v&lbrke=87f;1am&lbrksld=87j;1aq&lbrkslu=87h;1ao&lcaron=8u;64&lcedil=8s;62&lceil=6x4;122&lcub=3f;17&lcy=u3;c2&ldca=852;18v&ldquo=6cc;dz&ldquor=6ce;e3&ldrdhar=86f;19v&ldrushar=85n;195&ldsh=6nm;jz&le=6sk;st&leftarrow=6mo;hv&leftarrowtail=6n6;j6&leftharpoondown=6nx;kd&leftharpoonup=6nw;ka&leftleftarrows=6o7;l6&leftrightarrow=6ms;ic&leftrightarrows=6o6;l4&leftrightharpoons=6ob;lf&leftrightsquigarrow=6nh;jr&leftthreetimes=6vf;zl&leg=6vu;10g&leq=6sk;ss&leqq=6sm;t1&leqslant=8e5;1f0&les=8e5;1f1&lescc=8fc;1gm&lesdot=8e7;1f8&lesdoto=8e9;1fa&lesdotor=8eb;1fc&lesg=6vu,1e68;10d&lesges=8er;1g0&lessapprox=8ed;1fe&lessdot=6vq;104&lesseqgtr=6vu;10f&lesseqqgtr=8ej;1fq&lessgtr=6t2;uj&lesssim=6sy;u9&lfisht=870;1ag&lfloor=6x6;126&lfr=2ko9;1ld&lg=6t2;uk&lgE=8ep;1fy&lhard=6nx;kf&lharu=6nw;kc&lharul=86i;19y&lhblk=7es;14o&ljcy=ux;cv&ll=6sq;tm&llarr=6o7;l7&llcorner=6xq;12m&llhard=86j;19z&lltri=7i2;15w&lmidot=8w;66&lmoust=71s;131&lmoustache=71s;130&lnE=6so;tc&lnap=8eh;1fn&lnapprox=8eh;1fm&lne=8ef;1fj&lneq=8ef;1fi&lneqq=6so;tb&lnsim=6w6;10x&loang=7vw;175&loarr=6pp;mp&lobrk=7vq;16u&longleftarrow=7w5;178&longleftrightarrow=7w7;17e&longmapsto=7wc;17p&longrightarrow=7w6;17b&looparrowleft=6nf;jn&looparrowright=6ng;jp&lopar=879;1ak&lopf=2kpp;1mm&loplus=8bx;1d6&lotimes=8c4;1dc&lowast=6qf;o5&lowbar=2n;12&loz=7gq;15p&lozenge=7gq;15o&lozf=8a3;1ca&lpar=14;b&lparlt=87n;1au&lrarr=6o6;l5&lrcorner=6xr;12o&lrhar=6ob;lg&lrhard=86l;1a1&lrm=6by;di&lrtri=6v3;yx&lsaquo=6d5;ek&lscr=2kld;1k3&lsh=6nk;jw&lsim=6sy;ua&lsime=8el;1fu&lsimg=8en;1fw&lsqb=2j;w&lsquo=6c8;ds&lsquor=6ca;dw&lstrok=8y;68<=1o;n<cc=8fa;1gk<cir=8e1;1es<dot=6vq;105<hree=6vf;zm<imes=6vd;zj<larr=86u;1ac<quest=8e3;1eu<rPar=87q;1ax<ri=7gj;15n<rie=6us;yi<rif=7gi;15l&lurdshar=85m;194&luruhar=86e;19u&lvertneqq=6so,1e68;t9&lvnE=6so,1e68;ta&mDDot=6re;q4¯=4v;20&male=7k2;164&malt=7q8;16m&maltese=7q8;16l&map=6na;jg&mapsto=6na;jf&mapstodown=6nb;ji&mapstoleft=6n8;jb&mapstoup=6n9;jd&marker=7fy;152&mcomma=8bt;1d4&mcy=u4;c3&mdash=6c4;dn&measuredangle=6qp;ok&mfr=2koa;1le&mho=6jr;gjµ=51;29&mid=6qr;oq&midast=16;d&midcir=8hc;1j1·=53;2d&minus=6qa;nu&minusb=6u7;xj&minusd=6rc;q3&minusdu=8bu;1d5&mlcp=8gr;1ip&mldr=6cm;ec&mnplus=6qb;nw&models=6uf;xy&mopf=2kpq;1mn&mp=6qb;nx&mscr=2kle;1k4&mstpos=6ri;qf&mu=qk;9e&multimap=6uw;yp&mumap=6uw;yq&nGg=6vt,mw;10a&nGt=6sr,6he;tp&nGtv=6sr,mw;to&nLeftarrow=6od;lk&nLeftrightarrow=6oe;lm&nLl=6vs,mw;108&nLt=6sq,6he;tj&nLtv=6sq,mw;ti&nRightarrow=6of;lo&nVDash=6un;y7&nVdash=6um;y6&nabla=6pz;n8&nacute=90;6a&nang=6qo,6he;oh&nap=6rt;rb&napE=8ds,mw;1ei&napid=6rv,mw;re&napos=95;6f&napprox=6rt;ra&natur=7la;16g&natural=7la;16f&naturals=6j9;fw =4g;1e&nbump=6ry,mw;rm&nbumpe=6rz,mw;rr&ncap=8cj;1dp&ncaron=94;6e&ncedil=92;6c&ncong=6rr;r2&ncongdot=8dp,mw;1ee&ncup=8ci;1do&ncy=u5;c4&ndash=6c3;dm&ne=6sg;sl&neArr=6on;mb&nearhk=84k;18h&nearr=6mv;im&nearrow=6mv;il&nedot=6s0,mw;rv&nequiv=6si;sq&nesear=84o;18n&nesim=6rm,mw;qo&nexist=6pw;n3&nexists=6pw;n2&nfr=2kob;1lf&ngE=6sn,mw;t4&nge=6sx;u7&ngeq=6sx;u6&ngeqq=6sn,mw;t5&ngeqslant=8e6,mw;1f3&nges=8e6,mw;1f4&ngsim=6t1;uh&ngt=6sv;u1&ngtr=6sv;u0&nhArr=6oe;ln&nharr=6ni;ju&nhpar=8he;1j3&ni=6q3;nk&nis=6ws;11u&nisd=6wq;11s&niv=6q3;nj&njcy=uy;cw&nlArr=6od;ll&nlE=6sm,mw;sy&nlarr=6my;iu&nldr=6cl;ea&nle=6sw;u4&nleftarrow=6my;it&nleftrightarrow=6ni;jt&nleq=6sw;u3&nleqq=6sm,mw;sz&nleqslant=8e5,mw;1ex&nles=8e5,mw;1ey&nless=6su;tx&nlsim=6t0;uf&nlt=6su;ty&nltri=6wa;115&nltrie=6wc;11b&nmid=6qs;ou&nopf=2kpr;1mo¬=4s;1u¬in=6q1;ng¬inE=6wp,mw;11q¬indot=6wl,mw;11m¬inva=6q1;nf¬invb=6wn;11p¬invc=6wm;11o¬ni=6q4;nn¬niva=6q4;nm¬nivb=6wu;11w¬nivc=6wt;11v&npar=6qu;p4&nparallel=6qu;p2&nparsl=8hp,6hx;1j5&npart=6pu,mw;mw&npolint=8b8;1cu&npr=6tc;vd&nprcue=6w0;10q&npre=8fj,mw;1gw&nprec=6tc;vc&npreceq=8fj,mw;1gx&nrArr=6of;lp&nrarr=6mz;iw&nrarrc=84z,mw;18s&nrarrw=6n1,mw;ix&nrightarrow=6mz;iv&nrtri=6wb;118&nrtrie=6wd;11e&nsc=6td;vg&nsccue=6w1;10s&nsce=8fk,mw;1h2&nscr=2klf;1k5&nshortmid=6qs;os&nshortparallel=6qu;p1&nsim=6rl;qm&nsime=6ro;qx&nsimeq=6ro;qw&nsmid=6qs;ot&nspar=6qu;p3&nsqsube=6w2;10u&nsqsupe=6w3;10w&nsub=6tg;vs&nsubE=8g5,mw;1hv&nsube=6tk;w2&nsubset=6te,6he;vi&nsubseteq=6tk;w1&nsubseteqq=8g5,mw;1hw&nsucc=6td;vf&nsucceq=8fk,mw;1h3&nsup=6th;vt&nsupE=8g6,mw;1hz&nsupe=6tl;w5&nsupset=6tf,6he;vn&nsupseteq=6tl;w4&nsupseteqq=8g6,mw;1i0&ntgl=6t5;urñ=6p;42&ntlg=6t4;up&ntriangleleft=6wa;114&ntrianglelefteq=6wc;11a&ntriangleright=6wb;117&ntrianglerighteq=6wd;11d&nu=ql;9f&num=z;5&numero=6ja;fy&numsp=6br;d5&nvDash=6ul;y5&nvHarr=83o;17u&nvap=6rx,6he;ri&nvdash=6uk;y4&nvge=6sl,6he;su&nvgt=1q,6he;q&nvinfin=89q;1c5&nvlArr=83m;17s&nvle=6sk,6he;sr&nvlt=1o,6he;l&nvltrie=6us,6he;yf&nvrArr=83n;17t&nvrtrie=6ut,6he;yj&nvsim=6rg,6he;q6&nwArr=6om;ma&nwarhk=84j;18g&nwarr=6mu;ij&nwarrow=6mu;ii&nwnear=84n;18m&oS=79k;13hó=6r;44&oast=6u3;xd&ocir=6u2;xbô=6s;45&ocy=u6;c5&odash=6u5;xf&odblac=9d;6l&odiv=8c8;1dg&odot=6u1;x9&odsold=88s;1bn&oelig=9f;6n&ofcir=88v;1bp&ofr=2koc;1lg&ogon=kb;87ò=6q;43&ogt=88x;1br&ohbar=88l;1bi&ohm=q1;91&oint=6r2;pk&olarr=6nu;k7&olcir=88u;1bo&olcross=88r;1bm&oline=6da;en&olt=88w;1bq&omacr=99;6j&omega=qx;9u&omicron=qn;9h&omid=88m;1bj&ominus=6ty;x4&oopf=2kps;1mp&opar=88n;1bk&operp=88p;1bl&oplus=6tx;x2&or=6qw;p8&orarr=6nv;k9&ord=8d9;1ea&order=6k4;h1&orderof=6k4;h0ª=4q;1sº=56;2h&origof=6uu;yn&oror=8d2;1e4&orslope=8d3;1e5&orv=8d7;1e8&oscr=6k4;h2ø=6w;4a&osol=6u0;x7õ=6t;46&otimes=6tz;x6&otimesas=8c6;1deö=6u;47&ovbar=6yl;12x&par=6qt;oz¶=52;2a¶llel=6qt;ox&parsim=8hf;1j4&parsl=8hp;1j6&part=6pu;my&pcy=u7;c6&percnt=11;7&period=1a;h&permil=6cw;ed&perp=6ud;xw&pertenk=6cx;ee&pfr=2kod;1lh&phi=qu;9r&phiv=r9;a2&phmmat=6k3;gy&phone=7im;162&pi=qo;9i&pitchfork=6vo;101&piv=ra;a4&planck=6j3;fj&planckh=6j2;fh&plankv=6j3;fk&plus=17;f&plusacir=8bn;1cz&plusb=6u6;xh&pluscir=8bm;1cy&plusdo=6qc;nz&plusdu=8bp;1d1&pluse=8du;1el±=4x;23&plussim=8bq;1d2&plustwo=8br;1d3&pm=4x;24&pointint=8b9;1cv&popf=2kpt;1mq£=4j;1h&pr=6t6;uu&prE=8fn;1h7&prap=8fr;1he&prcue=6t8;v0&pre=8fj;1h0&prec=6t6;ut&precapprox=8fr;1hd&preccurlyeq=6t8;uz&preceq=8fj;1gz&precnapprox=8ft;1hh&precneqq=8fp;1h9&precnsim=6w8;10z&precsim=6ta;v5&prime=6cy;ef&primes=6jd;g2&prnE=8fp;1ha&prnap=8ft;1hi&prnsim=6w8;110&prod=6q7;np&profalar=6y6;12v&profline=6xe;12e&profsurf=6xf;12f&prop=6ql;oe&propto=6ql;oc&prsim=6ta;v6&prurel=6uo;y8&pscr=2klh;1k6&psi=qw;9t&puncsp=6bs;d6&qfr=2koe;1li&qint=8b0;1co&qopf=2kpu;1mr&qprime=6dz;es&qscr=2kli;1k7&quaternions=6j1;ff&quatint=8ba;1cw&quest=1r;t&questeq=6sf;si"=y;4&rAarr=6or;mh&rArr=6oi;lz&rAtail=84c;18b&rBarr=83z;181&rHar=86c;19s&race=6rh,mp;qb&racute=9h;6p&radic=6qi;o8&raemptyv=88j;1bg&rang=7vt;172&rangd=87m;1at&range=885;1b2&rangle=7vt;171»=57;2i&rarr=6mq;i6&rarrap=86t;1ab&rarrb=6p1;mm&rarrbfs=84g;18f&rarrc=84z;18t&rarrfs=84e;18d&rarrhk=6ne;jm&rarrlp=6ng;jq&rarrpl=85h;191&rarrsim=86s;1aa&rarrtl=6n7;j9&rarrw=6n1;iz&ratail=84a;189&ratio=6ra;pz&rationals=6je;g4&rbarr=83x;17y&rbbrk=7sj;16q&rbrace=3h;1b&rbrack=2l;y&rbrke=87g;1an&rbrksld=87i;1ap&rbrkslu=87k;1ar&rcaron=9l;6t&rcedil=9j;6r&rceil=6x5;124&rcub=3h;1c&rcy=u8;c7&rdca=853;18w&rdldhar=86h;19x&rdquo=6cd;e2&rdquor=6cd;e1&rdsh=6nn;k0&real=6jg;g9&realine=6jf;g6&realpart=6jg;g8&reals=6jh;gc&rect=7fx;151®=4u;1y&rfisht=871;1ah&rfloor=6x7;128&rfr=2kof;1lj&rhard=6o1;kr&rharu=6o0;ko&rharul=86k;1a0&rho=qp;9j&rhov=s1;ab&rightarrow=6mq;i4&rightarrowtail=6n7;j8&rightharpoondown=6o1;kp&rightharpoonup=6o0;km&rightleftarrows=6o4;kz&rightleftharpoons=6oc;lh&rightrightarrows=6o9;la&rightsquigarrow=6n1;iy&rightthreetimes=6vg;zn&ring=ka;86&risingdotseq=6s3;s3&rlarr=6o4;l0&rlhar=6oc;lj&rlm=6bz;dj&rmoust=71t;133&rmoustache=71t;132&rnmid=8ha;1iz&roang=7vx;176&roarr=6pq;mq&robrk=7vr;16w&ropar=87a;1al&ropf=2kpv;1ms&roplus=8by;1d7&rotimes=8c5;1dd&rpar=15;c&rpargt=87o;1av&rppolint=8b6;1cs&rrarr=6o9;lb&rsaquo=6d6;el&rscr=2klj;1k8&rsh=6nl;jy&rsqb=2l;z&rsquo=6c9;dv&rsquor=6c9;du&rthree=6vg;zo&rtimes=6ve;zk&rtri=7g9;15d&rtrie=6ut;ym&rtrif=7g8;15b&rtriltri=89a;1by&ruluhar=86g;19w&rx=6ji;ge&sacute=9n;6v&sbquo=6ca;dx&sc=6t7;ux&scE=8fo;1h8&scap=8fs;1hg&scaron=9t;71&sccue=6t9;v3&sce=8fk;1h6&scedil=9r;6z&scirc=9p;6x&scnE=8fq;1hc&scnap=8fu;1hk&scnsim=6w9;112&scpolint=8b7;1ct&scsim=6tb;va&scy=u9;c8&sdot=6v9;zd&sdotb=6u9;xn&sdote=8di;1ec&seArr=6oo;mc&searhk=84l;18j&searr=6mw;ip&searrow=6mw;io§=4n;1l&semi=1n;k&seswar=84p;18p&setminus=6qe;o2&setmn=6qe;o4&sext=7qu;16n&sfr=2kog;1lk&sfrown=6xu;12q&sharp=7lb;16h&shchcy=uh;cg&shcy=ug;cf&shortmid=6qr;oo&shortparallel=6qt;ow­=4t;1v&sigma=qr;9n&sigmaf=qq;9l&sigmav=qq;9m&sim=6rg;qa&simdot=8dm;1ed&sime=6rn;qu&simeq=6rn;qt&simg=8f2;1gb&simgE=8f4;1gd&siml=8f1;1ga&simlE=8f3;1gc&simne=6rq;r0&simplus=8bo;1d0&simrarr=86q;1a8&slarr=6mo;hw&smallsetminus=6qe;o0&smashp=8c3;1db&smeparsl=89w;1c7&smid=6qr;op&smile=6xv;12t&smt=8fe;1go&smte=8fg;1gr&smtes=8fg,1e68;1gq&softcy=uk;cj&sol=1b;i&solb=890;1bu&solbar=6yn;12y&sopf=2kpw;1mt&spades=7kw;166&spadesuit=7kw;165&spar=6qt;oy&sqcap=6tv;wx&sqcaps=6tv,1e68;wv&sqcup=6tw;x0&sqcups=6tw,1e68;wy&sqsub=6tr;wk&sqsube=6tt;wr&sqsubset=6tr;wj&sqsubseteq=6tt;wq&sqsup=6ts;wo&sqsupe=6tu;wu&sqsupset=6ts;wn&sqsupseteq=6tu;wt&squ=7fl;14v&square=7fl;14u&squarf=7fu;14y&squf=7fu;14z&srarr=6mq;i5&sscr=2klk;1k9&ssetmn=6qe;o3&ssmile=6xv;12s&sstarf=6va;ze&star=7ie;161&starf=7id;160&straightepsilon=s5;ac&straightphi=r9;a0&strns=4v;1z&sub=6te;vl&subE=8g5;1hy&subdot=8fx;1hn&sube=6ti;vw&subedot=8g3;1ht&submult=8g1;1hr&subnE=8gb;1i8&subne=6tm;w9&subplus=8fz;1hp&subrarr=86x;1ae&subset=6te;vk&subseteq=6ti;vv&subseteqq=8g5;1hx&subsetneq=6tm;w8&subsetneqq=8gb;1i7&subsim=8g7;1i3&subsub=8gl;1ij&subsup=8gj;1ih&succ=6t7;uw&succapprox=8fs;1hf&succcurlyeq=6t9;v2&succeq=8fk;1h5&succnapprox=8fu;1hj&succneqq=8fq;1hb&succnsim=6w9;111&succsim=6tb;v9&sum=6q9;nt&sung=7l6;16d&sup=6tf;vr¹=55;2g²=4y;25³=4z;26&supE=8g6;1i2&supdot=8fy;1ho&supdsub=8go;1im&supe=6tj;vz&supedot=8g4;1hu&suphsol=7ux;16s&suphsub=8gn;1il&suplarr=86z;1af&supmult=8g2;1hs&supnE=8gc;1ic&supne=6tn;wd&supplus=8g0;1hq&supset=6tf;vq&supseteq=6tj;vy&supseteqq=8g6;1i1&supsetneq=6tn;wc&supsetneqq=8gc;1ib&supsim=8g8;1i4&supsub=8gk;1ii&supsup=8gm;1ik&swArr=6op;md&swarhk=84m;18l&swarr=6mx;is&swarrow=6mx;ir&swnwar=84q;18rß=67;3k&target=6xi;12h&tau=qs;9o&tbrk=71w;135&tcaron=9x;75&tcedil=9v;73&tcy=ua;c9&tdot=6hn;f4&telrec=6xh;12g&tfr=2koh;1ll&there4=6r8;pv&therefore=6r8;pu&theta=qg;9a&thetasym=r5;9v&thetav=r5;9x&thickapprox=6rs;r3&thicksim=6rg;q7&thinsp=6bt;d8&thkap=6rs;r7&thksim=6rg;q8þ=72;4g&tilde=kc;89×=5z;3c×b=6u8;xl×bar=8c1;1da×d=8c0;1d9&tint=6r1;ph&toea=84o;18o&top=6uc;xt&topbot=6ye;12w&topcir=8hd;1j2&topf=2kpx;1mu&topfork=8gq;1io&tosa=84p;18q&tprime=6d0;eh&trade=6jm;gg&triangle=7g5;158&triangledown=7gf;15i&triangleleft=7gj;15m&trianglelefteq=6us;yh&triangleq=6sc;sg&triangleright=7g9;15c&trianglerighteq=6ut;yl&tridot=7ho;15r&trie=6sc;sh&triminus=8ca;1di&triplus=8c9;1dh&trisb=899;1bx&tritime=8cb;1dj&trpezium=736;13d&tscr=2kll;1ka&tscy=ue;cd&tshcy=uz;cx&tstrok=9z;77&twixt=6ss;tu&twoheadleftarrow=6n2;j0&twoheadrightarrow=6n4;j3&uArr=6oh;lv&uHar=86b;19rú=6y;4c&uarr=6mp;i1&ubrcy=v2;cz&ubreve=a5;7dû=6z;4d&ucy=ub;ca&udarr=6o5;l2&udblac=a9;7h&udhar=86m;1a3&ufisht=872;1ai&ufr=2koi;1lmù=6x;4b&uharl=6nz;kl&uharr=6ny;ki&uhblk=7eo;14n&ulcorn=6xo;12j&ulcorner=6xo;12i&ulcrop=6xb;12c&ultri=7i0;15u&umacr=a3;7b¨=4o;1p&uogon=ab;7j&uopf=2kpy;1mv&uparrow=6mp;i0&updownarrow=6mt;if&upharpoonleft=6nz;kj&upharpoonright=6ny;kg&uplus=6tq;wg&upsi=qt;9q&upsih=r6;9y&upsilon=qt;9p&upuparrows=6o8;l8&urcorn=6xp;12l&urcorner=6xp;12k&urcrop=6xa;12b&uring=a7;7f&urtri=7i1;15v&uscr=2klm;1kb&utdot=6wg;11h&utilde=a1;79&utri=7g5;159&utrif=7g4;157&uuarr=6o8;l9ü=70;4e&uwangle=887;1b4&vArr=6ol;m9&vBar=8h4;1iu&vBarv=8h5;1iv&vDash=6ug;y0&vangrt=87w;1az&varepsilon=s5;ad&varkappa=s0;a8&varnothing=6px;n4&varphi=r9;a1&varpi=ra;a3&varpropto=6ql;ob&varr=6mt;ig&varrho=s1;aa&varsigma=qq;9k&varsubsetneq=6tm,1e68;w6&varsubsetneqq=8gb,1e68;1i5&varsupsetneq=6tn,1e68;wa&varsupsetneqq=8gc,1e68;1i9&vartheta=r5;9w&vartriangleleft=6uq;y9&vartriangleright=6ur;yc&vcy=tu;bt&vdash=6ua;xp&vee=6qw;p7&veebar=6uz;yu&veeeq=6sa;sf&vellip=6we;11f&verbar=3g;19&vert=3g;1a&vfr=2koj;1ln&vltri=6uq;yb&vnsub=6te,6he;vj&vnsup=6tf,6he;vo&vopf=2kpz;1mw&vprop=6ql;od&vrtri=6ur;ye&vscr=2kln;1kc&vsubnE=8gb,1e68;1i6&vsubne=6tm,1e68;w7&vsupnE=8gc,1e68;1ia&vsupne=6tn,1e68;wb&vzigzag=87u;1ay&wcirc=ad;7l&wedbar=8db;1eb&wedge=6qv;p5&wedgeq=6s9;se&weierp=6jc;g0&wfr=2kok;1lo&wopf=2kq0;1mx&wp=6jc;g1&wr=6rk;qk&wreath=6rk;qj&wscr=2klo;1kd&xcap=6v6;z6&xcirc=7hr;15t&xcup=6v7;z9&xdtri=7gd;15f&xfr=2kol;1lp&xhArr=7wa;17o&xharr=7w7;17f&xi=qm;9g&xlArr=7w8;17i&xlarr=7w5;179&xmap=7wc;17q&xnis=6wr;11t&xodot=8ao;1ce&xopf=2kq1;1my&xoplus=8ap;1cg&xotime=8aq;1ci&xrArr=7w9;17l&xrarr=7w6;17c&xscr=2klp;1ke&xsqcup=8au;1cm&xuplus=8as;1ck&xutri=7g3;155&xvee=6v5;z2&xwedge=6v4;yzý=71;4f&yacy=un;cm&ycirc=af;7n&ycy=uj;ci¥=4l;1j&yfr=2kom;1lq&yicy=uv;ct&yopf=2kq2;1mz&yscr=2klq;1kf&yucy=um;clÿ=73;4h&zacute=ai;7q&zcaron=am;7u&zcy=tz;by&zdot=ak;7s&zeetrf=6js;gk&zeta=qe;98&zfr=2kon;1lr&zhcy=ty;bx&zigrarr=6ot;mi&zopf=2kq3;1n0&zscr=2klr;1kg&zwj=6bx;dh&zwnj=6bw;dg&"; +} diff --git a/src/main/java/org/jsoup/nodes/entities-base.properties b/src/main/java/org/jsoup/nodes/entities-base.properties deleted file mode 100644 index 465bd33b57..0000000000 --- a/src/main/java/org/jsoup/nodes/entities-base.properties +++ /dev/null @@ -1,106 +0,0 @@ -AElig=5i;1c -AMP=12;2 -Aacute=5d;17 -Acirc=5e;18 -Agrave=5c;16 -Aring=5h;1b -Atilde=5f;19 -Auml=5g;1a -COPY=4p;h -Ccedil=5j;1d -ETH=5s;1m -Eacute=5l;1f -Ecirc=5m;1g -Egrave=5k;1e -Euml=5n;1h -GT=1q;6 -Iacute=5p;1j -Icirc=5q;1k -Igrave=5o;1i -Iuml=5r;1l -LT=1o;4 -Ntilde=5t;1n -Oacute=5v;1p -Ocirc=5w;1q -Ograve=5u;1o -Oslash=60;1u -Otilde=5x;1r -Ouml=5y;1s -QUOT=y;0 -REG=4u;n -THORN=66;20 -Uacute=62;1w -Ucirc=63;1x -Ugrave=61;1v -Uuml=64;1y -Yacute=65;1z -aacute=69;23 -acirc=6a;24 -acute=50;u -aelig=6e;28 -agrave=68;22 -amp=12;3 -aring=6d;27 -atilde=6b;25 -auml=6c;26 -brvbar=4m;e -ccedil=6f;29 -cedil=54;y -cent=4i;a -copy=4p;i -curren=4k;c -deg=4w;q -divide=6v;2p -eacute=6h;2b -ecirc=6i;2c -egrave=6g;2a -eth=6o;2i -euml=6j;2d -frac12=59;13 -frac14=58;12 -frac34=5a;14 -gt=1q;7 -iacute=6l;2f -icirc=6m;2g -iexcl=4h;9 -igrave=6k;2e -iquest=5b;15 -iuml=6n;2h -laquo=4r;k -lt=1o;5 -macr=4v;p -micro=51;v -middot=53;x -nbsp=4g;8 -not=4s;l -ntilde=6p;2j -oacute=6r;2l -ocirc=6s;2m -ograve=6q;2k -ordf=4q;j -ordm=56;10 -oslash=6w;2q -otilde=6t;2n -ouml=6u;2o -para=52;w -plusmn=4x;r -pound=4j;b -quot=y;1 -raquo=57;11 -reg=4u;o -sect=4n;f -shy=4t;m -sup1=55;z -sup2=4y;s -sup3=4z;t -szlig=67;21 -thorn=72;2w -times=5z;1t -uacute=6y;2s -ucirc=6z;2t -ugrave=6x;2r -uml=4o;g -uuml=70;2u -yacute=71;2v -yen=4l;d -yuml=73;2x diff --git a/src/main/java/org/jsoup/nodes/entities-full.properties b/src/main/java/org/jsoup/nodes/entities-full.properties deleted file mode 100644 index 76eadf2a69..0000000000 --- a/src/main/java/org/jsoup/nodes/entities-full.properties +++ /dev/null @@ -1,2125 +0,0 @@ -AElig=5i;2v -AMP=12;8 -Aacute=5d;2p -Abreve=76;4k -Acirc=5e;2q -Acy=sw;av -Afr=2kn8;1kh -Agrave=5c;2o -Alpha=pd;8d -Amacr=74;4i -And=8cz;1e1 -Aogon=78;4m -Aopf=2koo;1ls -ApplyFunction=6e9;ew -Aring=5h;2t -Ascr=2kkc;1jc -Assign=6s4;s6 -Atilde=5f;2r -Auml=5g;2s -Backslash=6qe;o1 -Barv=8h3;1it -Barwed=6x2;120 -Bcy=sx;aw -Because=6r9;pw -Bernoullis=6jw;gn -Beta=pe;8e -Bfr=2kn9;1ki -Bopf=2kop;1lt -Breve=k8;82 -Bscr=6jw;gp -Bumpeq=6ry;ro -CHcy=tj;bi -COPY=4p;1q -Cacute=7a;4o -Cap=6vm;zz -CapitalDifferentialD=6kl;h8 -Cayleys=6jx;gq -Ccaron=7g;4u -Ccedil=5j;2w -Ccirc=7c;4q -Cconint=6r4;pn -Cdot=7e;4s -Cedilla=54;2e -CenterDot=53;2b -Cfr=6jx;gr -Chi=pz;8y -CircleDot=6u1;x8 -CircleMinus=6ty;x3 -CirclePlus=6tx;x1 -CircleTimes=6tz;x5 -ClockwiseContourIntegral=6r6;pp -CloseCurlyDoubleQuote=6cd;e0 -CloseCurlyQuote=6c9;dt -Colon=6rb;q1 -Colone=8dw;1en -Congruent=6sh;sn -Conint=6r3;pm -ContourIntegral=6r2;pi -Copf=6iq;f7 -Coproduct=6q8;nq -CounterClockwiseContourIntegral=6r7;pr -Cross=8bz;1d8 -Cscr=2kke;1jd -Cup=6vn;100 -CupCap=6rx;rk -DD=6kl;h9 -DDotrahd=841;184 -DJcy=si;ai -DScy=sl;al -DZcy=sv;au -Dagger=6ch;e7 -Darr=6n5;j5 -Dashv=8h0;1ir -Dcaron=7i;4w -Dcy=t0;az -Del=6pz;n9 -Delta=pg;8g -Dfr=2knb;1kj -DiacriticalAcute=50;27 -DiacriticalDot=k9;84 -DiacriticalDoubleAcute=kd;8a -DiacriticalGrave=2o;13 -DiacriticalTilde=kc;88 -Diamond=6v8;za -DifferentialD=6km;ha -Dopf=2kor;1lu -Dot=4o;1n -DotDot=6ho;f5 -DotEqual=6s0;rw -DoubleContourIntegral=6r3;pl -DoubleDot=4o;1m -DoubleDownArrow=6oj;m0 -DoubleLeftArrow=6og;lq -DoubleLeftRightArrow=6ok;m3 -DoubleLeftTee=8h0;1iq -DoubleLongLeftArrow=7w8;17g -DoubleLongLeftRightArrow=7wa;17m -DoubleLongRightArrow=7w9;17j -DoubleRightArrow=6oi;lw -DoubleRightTee=6ug;xz -DoubleUpArrow=6oh;lt -DoubleUpDownArrow=6ol;m7 -DoubleVerticalBar=6qt;ov -DownArrow=6mr;i8 -DownArrowBar=843;186 -DownArrowUpArrow=6ph;mn -DownBreve=lt;8c -DownLeftRightVector=85s;198 -DownLeftTeeVector=866;19m -DownLeftVector=6nx;ke -DownLeftVectorBar=85y;19e -DownRightTeeVector=867;19n -DownRightVector=6o1;kq -DownRightVectorBar=85z;19f -DownTee=6uc;xs -DownTeeArrow=6nb;jh -Downarrow=6oj;m1 -Dscr=2kkf;1je -Dstrok=7k;4y -ENG=96;6g -ETH=5s;35 -Eacute=5l;2y -Ecaron=7u;56 -Ecirc=5m;2z -Ecy=tp;bo -Edot=7q;52 -Efr=2knc;1kk -Egrave=5k;2x -Element=6q0;na -Emacr=7m;50 -EmptySmallSquare=7i3;15x -EmptyVerySmallSquare=7fv;150 -Eogon=7s;54 -Eopf=2kos;1lv -Epsilon=ph;8h -Equal=8dx;1eo -EqualTilde=6rm;qp -Equilibrium=6oc;li -Escr=6k0;gu -Esim=8dv;1em -Eta=pj;8j -Euml=5n;30 -Exists=6pv;mz -ExponentialE=6kn;hc -Fcy=tg;bf -Ffr=2knd;1kl -FilledSmallSquare=7i4;15y -FilledVerySmallSquare=7fu;14w -Fopf=2kot;1lw -ForAll=6ps;ms -Fouriertrf=6k1;gv -Fscr=6k1;gw -GJcy=sj;aj -GT=1q;r -Gamma=pf;8f -Gammad=rg;a5 -Gbreve=7y;5a -Gcedil=82;5e -Gcirc=7w;58 -Gcy=sz;ay -Gdot=80;5c -Gfr=2kne;1km -Gg=6vt;10c -Gopf=2kou;1lx -GreaterEqual=6sl;sv -GreaterEqualLess=6vv;10i -GreaterFullEqual=6sn;t6 -GreaterGreater=8f6;1gh -GreaterLess=6t3;ul -GreaterSlantEqual=8e6;1f5 -GreaterTilde=6sz;ub -Gscr=2kki;1jf -Gt=6sr;tr -HARDcy=tm;bl -Hacek=jr;80 -Hat=2m;10 -Hcirc=84;5f -Hfr=6j0;fe -HilbertSpace=6iz;fa -Hopf=6j1;fg -HorizontalLine=7b4;13i -Hscr=6iz;fc -Hstrok=86;5h -HumpDownHump=6ry;rn -HumpEqual=6rz;rs -IEcy=t1;b0 -IJlig=8i;5s -IOcy=sh;ah -Iacute=5p;32 -Icirc=5q;33 -Icy=t4;b3 -Idot=8g;5p -Ifr=6j5;fq -Igrave=5o;31 -Im=6j5;fr -Imacr=8a;5l -ImaginaryI=6ko;hf -Implies=6oi;ly -Int=6r0;pf -Integral=6qz;pd -Intersection=6v6;z4 -InvisibleComma=6eb;f0 -InvisibleTimes=6ea;ey -Iogon=8e;5n -Iopf=2kow;1ly -Iota=pl;8l -Iscr=6j4;fn -Itilde=88;5j -Iukcy=sm;am -Iuml=5r;34 -Jcirc=8k;5u -Jcy=t5;b4 -Jfr=2knh;1kn -Jopf=2kox;1lz -Jscr=2kkl;1jg -Jsercy=so;ao -Jukcy=sk;ak -KHcy=th;bg -KJcy=ss;as -Kappa=pm;8m -Kcedil=8m;5w -Kcy=t6;b5 -Kfr=2kni;1ko -Kopf=2koy;1m0 -Kscr=2kkm;1jh -LJcy=sp;ap -LT=1o;m -Lacute=8p;5z -Lambda=pn;8n -Lang=7vu;173 -Laplacetrf=6j6;fs -Larr=6n2;j1 -Lcaron=8t;63 -Lcedil=8r;61 -Lcy=t7;b6 -LeftAngleBracket=7vs;16x -LeftArrow=6mo;hu -LeftArrowBar=6p0;mj -LeftArrowRightArrow=6o6;l3 -LeftCeiling=6x4;121 -LeftDoubleBracket=7vq;16t -LeftDownTeeVector=869;19p -LeftDownVector=6o3;kw -LeftDownVectorBar=861;19h -LeftFloor=6x6;125 -LeftRightArrow=6ms;ib -LeftRightVector=85q;196 -LeftTee=6ub;xq -LeftTeeArrow=6n8;ja -LeftTeeVector=862;19i -LeftTriangle=6uq;ya -LeftTriangleBar=89b;1c0 -LeftTriangleEqual=6us;yg -LeftUpDownVector=85t;199 -LeftUpTeeVector=868;19o -LeftUpVector=6nz;kk -LeftUpVectorBar=860;19g -LeftVector=6nw;kb -LeftVectorBar=85u;19a -Leftarrow=6og;lr -Leftrightarrow=6ok;m4 -LessEqualGreater=6vu;10e -LessFullEqual=6sm;t0 -LessGreater=6t2;ui -LessLess=8f5;1gf -LessSlantEqual=8e5;1ez -LessTilde=6sy;u8 -Lfr=2knj;1kp -Ll=6vs;109 -Lleftarrow=6oq;me -Lmidot=8v;65 -LongLeftArrow=7w5;177 -LongLeftRightArrow=7w7;17d -LongRightArrow=7w6;17a -Longleftarrow=7w8;17h -Longleftrightarrow=7wa;17n -Longrightarrow=7w9;17k -Lopf=2koz;1m1 -LowerLeftArrow=6mx;iq -LowerRightArrow=6mw;in -Lscr=6j6;fu -Lsh=6nk;jv -Lstrok=8x;67 -Lt=6sq;tl -Map=83p;17v -Mcy=t8;b7 -MediumSpace=6e7;eu -Mellintrf=6k3;gx -Mfr=2knk;1kq -MinusPlus=6qb;nv -Mopf=2kp0;1m2 -Mscr=6k3;gz -Mu=po;8o -NJcy=sq;aq -Nacute=8z;69 -Ncaron=93;6d -Ncedil=91;6b -Ncy=t9;b8 -NegativeMediumSpace=6bv;dc -NegativeThickSpace=6bv;dd -NegativeThinSpace=6bv;de -NegativeVeryThinSpace=6bv;db -NestedGreaterGreater=6sr;tq -NestedLessLess=6sq;tk -NewLine=a;1 -Nfr=2knl;1kr -NoBreak=6e8;ev -NonBreakingSpace=4g;1d -Nopf=6j9;fx -Not=8h8;1ix -NotCongruent=6si;sp -NotCupCap=6st;tv -NotDoubleVerticalBar=6qu;p0 -NotElement=6q1;ne -NotEqual=6sg;sk -NotEqualTilde=6rm,mw;qn -NotExists=6pw;n1 -NotGreater=6sv;tz -NotGreaterEqual=6sx;u5 -NotGreaterFullEqual=6sn,mw;t3 -NotGreaterGreater=6sr,mw;tn -NotGreaterLess=6t5;uq -NotGreaterSlantEqual=8e6,mw;1f2 -NotGreaterTilde=6t1;ug -NotHumpDownHump=6ry,mw;rl -NotHumpEqual=6rz,mw;rq -NotLeftTriangle=6wa;113 -NotLeftTriangleBar=89b,mw;1bz -NotLeftTriangleEqual=6wc;119 -NotLess=6su;tw -NotLessEqual=6sw;u2 -NotLessGreater=6t4;uo -NotLessLess=6sq,mw;th -NotLessSlantEqual=8e5,mw;1ew -NotLessTilde=6t0;ue -NotNestedGreaterGreater=8f6,mw;1gg -NotNestedLessLess=8f5,mw;1ge -NotPrecedes=6tc;vb -NotPrecedesEqual=8fj,mw;1gv -NotPrecedesSlantEqual=6w0;10p -NotReverseElement=6q4;nl -NotRightTriangle=6wb;116 -NotRightTriangleBar=89c,mw;1c1 -NotRightTriangleEqual=6wd;11c -NotSquareSubset=6tr,mw;wh -NotSquareSubsetEqual=6w2;10t -NotSquareSuperset=6ts,mw;wl -NotSquareSupersetEqual=6w3;10v -NotSubset=6te,6he;vh -NotSubsetEqual=6tk;w0 -NotSucceeds=6td;ve -NotSucceedsEqual=8fk,mw;1h1 -NotSucceedsSlantEqual=6w1;10r -NotSucceedsTilde=6tb,mw;v7 -NotSuperset=6tf,6he;vm -NotSupersetEqual=6tl;w3 -NotTilde=6rl;ql -NotTildeEqual=6ro;qv -NotTildeFullEqual=6rr;r1 -NotTildeTilde=6rt;r9 -NotVerticalBar=6qs;or -Nscr=2kkp;1ji -Ntilde=5t;36 -Nu=pp;8p -OElig=9e;6m -Oacute=5v;38 -Ocirc=5w;39 -Ocy=ta;b9 -Odblac=9c;6k -Ofr=2knm;1ks -Ograve=5u;37 -Omacr=98;6i -Omega=q1;90 -Omicron=pr;8r -Oopf=2kp2;1m3 -OpenCurlyDoubleQuote=6cc;dy -OpenCurlyQuote=6c8;dr -Or=8d0;1e2 -Oscr=2kkq;1jj -Oslash=60;3d -Otilde=5x;3a -Otimes=8c7;1df -Ouml=5y;3b -OverBar=6da;em -OverBrace=732;13b -OverBracket=71w;134 -OverParenthesis=730;139 -PartialD=6pu;mx -Pcy=tb;ba -Pfr=2knn;1kt -Phi=py;8x -Pi=ps;8s -PlusMinus=4x;22 -Poincareplane=6j0;fd -Popf=6jd;g3 -Pr=8fv;1hl -Precedes=6t6;us -PrecedesEqual=8fj;1gy -PrecedesSlantEqual=6t8;uy -PrecedesTilde=6ta;v4 -Prime=6cz;eg -Product=6q7;no -Proportion=6rb;q0 -Proportional=6ql;oa -Pscr=2kkr;1jk -Psi=q0;8z -QUOT=y;3 -Qfr=2kno;1ku -Qopf=6je;g5 -Qscr=2kks;1jl -RBarr=840;183 -REG=4u;1x -Racute=9g;6o -Rang=7vv;174 -Rarr=6n4;j4 -Rarrtl=846;187 -Rcaron=9k;6s -Rcedil=9i;6q -Rcy=tc;bb -Re=6jg;gb -ReverseElement=6q3;nh -ReverseEquilibrium=6ob;le -ReverseUpEquilibrium=86n;1a4 -Rfr=6jg;ga -Rho=pt;8t -RightAngleBracket=7vt;170 -RightArrow=6mq;i3 -RightArrowBar=6p1;ml -RightArrowLeftArrow=6o4;ky -RightCeiling=6x5;123 -RightDoubleBracket=7vr;16v -RightDownTeeVector=865;19l -RightDownVector=6o2;kt -RightDownVectorBar=85x;19d -RightFloor=6x7;127 -RightTee=6ua;xo -RightTeeArrow=6na;je -RightTeeVector=863;19j -RightTriangle=6ur;yd -RightTriangleBar=89c;1c2 -RightTriangleEqual=6ut;yk -RightUpDownVector=85r;197 -RightUpTeeVector=864;19k -RightUpVector=6ny;kh -RightUpVectorBar=85w;19c -RightVector=6o0;kn -RightVectorBar=85v;19b -Rightarrow=6oi;lx -Ropf=6jh;gd -RoundImplies=86o;1a6 -Rrightarrow=6or;mg -Rscr=6jf;g7 -Rsh=6nl;jx -RuleDelayed=8ac;1cb -SHCHcy=tl;bk -SHcy=tk;bj -SOFTcy=to;bn -Sacute=9m;6u -Sc=8fw;1hm -Scaron=9s;70 -Scedil=9q;6y -Scirc=9o;6w -Scy=td;bc -Sfr=2knq;1kv -ShortDownArrow=6mr;i7 -ShortLeftArrow=6mo;ht -ShortRightArrow=6mq;i2 -ShortUpArrow=6mp;hy -Sigma=pv;8u -SmallCircle=6qg;o6 -Sopf=2kp6;1m4 -Sqrt=6qi;o9 -Square=7fl;14t -SquareIntersection=6tv;ww -SquareSubset=6tr;wi -SquareSubsetEqual=6tt;wp -SquareSuperset=6ts;wm -SquareSupersetEqual=6tu;ws -SquareUnion=6tw;wz -Sscr=2kku;1jm -Star=6va;zf -Sub=6vk;zw -Subset=6vk;zv -SubsetEqual=6ti;vu -Succeeds=6t7;uv -SucceedsEqual=8fk;1h4 -SucceedsSlantEqual=6t9;v1 -SucceedsTilde=6tb;v8 -SuchThat=6q3;ni -Sum=6q9;ns -Sup=6vl;zy -Superset=6tf;vp -SupersetEqual=6tj;vx -Supset=6vl;zx -THORN=66;3j -TRADE=6jm;gf -TSHcy=sr;ar -TScy=ti;bh -Tab=9;0 -Tau=pw;8v -Tcaron=9w;74 -Tcedil=9u;72 -Tcy=te;bd -Tfr=2knr;1kw -Therefore=6r8;pt -Theta=pk;8k -ThickSpace=6e7,6bu;et -ThinSpace=6bt;d7 -Tilde=6rg;q9 -TildeEqual=6rn;qs -TildeFullEqual=6rp;qy -TildeTilde=6rs;r4 -Topf=2kp7;1m5 -TripleDot=6hn;f3 -Tscr=2kkv;1jn -Tstrok=9y;76 -Uacute=62;3f -Uarr=6n3;j2 -Uarrocir=85l;193 -Ubrcy=su;at -Ubreve=a4;7c -Ucirc=63;3g -Ucy=tf;be -Udblac=a8;7g -Ufr=2kns;1kx -Ugrave=61;3e -Umacr=a2;7a -UnderBar=2n;11 -UnderBrace=733;13c -UnderBracket=71x;136 -UnderParenthesis=731;13a -Union=6v7;z8 -UnionPlus=6tq;wf -Uogon=aa;7i -Uopf=2kp8;1m6 -UpArrow=6mp;hz -UpArrowBar=842;185 -UpArrowDownArrow=6o5;l1 -UpDownArrow=6mt;ie -UpEquilibrium=86m;1a2 -UpTee=6ud;xv -UpTeeArrow=6n9;jc -Uparrow=6oh;lu -Updownarrow=6ol;m8 -UpperLeftArrow=6mu;ih -UpperRightArrow=6mv;ik -Upsi=r6;9z -Upsilon=px;8w -Uring=a6;7e -Uscr=2kkw;1jo -Utilde=a0;78 -Uuml=64;3h -VDash=6uj;y3 -Vbar=8h7;1iw -Vcy=sy;ax -Vdash=6uh;y1 -Vdashl=8h2;1is -Vee=6v5;z3 -Verbar=6c6;dp -Vert=6c6;dq -VerticalBar=6qr;on -VerticalLine=3g;18 -VerticalSeparator=7rs;16o -VerticalTilde=6rk;qi -VeryThinSpace=6bu;d9 -Vfr=2knt;1ky -Vopf=2kp9;1m7 -Vscr=2kkx;1jp -Vvdash=6ui;y2 -Wcirc=ac;7k -Wedge=6v4;z0 -Wfr=2knu;1kz -Wopf=2kpa;1m8 -Wscr=2kky;1jq -Xfr=2knv;1l0 -Xi=pq;8q -Xopf=2kpb;1m9 -Xscr=2kkz;1jr -YAcy=tr;bq -YIcy=sn;an -YUcy=tq;bp -Yacute=65;3i -Ycirc=ae;7m -Ycy=tn;bm -Yfr=2knw;1l1 -Yopf=2kpc;1ma -Yscr=2kl0;1js -Yuml=ag;7o -ZHcy=t2;b1 -Zacute=ah;7p -Zcaron=al;7t -Zcy=t3;b2 -Zdot=aj;7r -ZeroWidthSpace=6bv;df -Zeta=pi;8i -Zfr=6js;gl -Zopf=6jo;gi -Zscr=2kl1;1jt -aacute=69;3m -abreve=77;4l -ac=6ri;qg -acE=6ri,mr;qe -acd=6rj;qh -acirc=6a;3n -acute=50;28 -acy=ts;br -aelig=6e;3r -af=6e9;ex -afr=2kny;1l2 -agrave=68;3l -alefsym=6k5;h3 -aleph=6k5;h4 -alpha=q9;92 -amacr=75;4j -amalg=8cf;1dm -amp=12;9 -and=6qv;p6 -andand=8d1;1e3 -andd=8d8;1e9 -andslope=8d4;1e6 -andv=8d6;1e7 -ang=6qo;oj -ange=884;1b1 -angle=6qo;oi -angmsd=6qp;ol -angmsdaa=888;1b5 -angmsdab=889;1b6 -angmsdac=88a;1b7 -angmsdad=88b;1b8 -angmsdae=88c;1b9 -angmsdaf=88d;1ba -angmsdag=88e;1bb -angmsdah=88f;1bc -angrt=6qn;og -angrtvb=6v2;yw -angrtvbd=87x;1b0 -angsph=6qq;om -angst=5h;2u -angzarr=70c;12z -aogon=79;4n -aopf=2kpe;1mb -ap=6rs;r8 -apE=8ds;1ej -apacir=8dr;1eh -ape=6ru;rd -apid=6rv;rf -apos=13;a -approx=6rs;r5 -approxeq=6ru;rc -aring=6d;3q -ascr=2kl2;1ju -ast=16;e -asymp=6rs;r6 -asympeq=6rx;rj -atilde=6b;3o -auml=6c;3p -awconint=6r7;ps -awint=8b5;1cr -bNot=8h9;1iy -backcong=6rw;rg -backepsilon=s6;af -backprime=6d1;ei -backsim=6rh;qc -backsimeq=6vh;zp -barvee=6v1;yv -barwed=6x1;11y -barwedge=6x1;11x -bbrk=71x;137 -bbrktbrk=71y;138 -bcong=6rw;rh -bcy=tt;bs -bdquo=6ce;e4 -becaus=6r9;py -because=6r9;px -bemptyv=88g;1bd -bepsi=s6;ag -bernou=6jw;go -beta=qa;93 -beth=6k6;h5 -between=6ss;tt -bfr=2knz;1l3 -bigcap=6v6;z5 -bigcirc=7hr;15s -bigcup=6v7;z7 -bigodot=8ao;1cd -bigoplus=8ap;1cf -bigotimes=8aq;1ch -bigsqcup=8au;1cl -bigstar=7id;15z -bigtriangledown=7gd;15e -bigtriangleup=7g3;154 -biguplus=8as;1cj -bigvee=6v5;z1 -bigwedge=6v4;yy -bkarow=83x;17x -blacklozenge=8a3;1c9 -blacksquare=7fu;14x -blacktriangle=7g4;156 -blacktriangledown=7ge;15g -blacktriangleleft=7gi;15k -blacktriangleright=7g8;15a -blank=74z;13f -blk12=7f6;14r -blk14=7f5;14q -blk34=7f7;14s -block=7ew;14p -bne=1p,6hx;o -bnequiv=6sh,6hx;sm -bnot=6xc;12d -bopf=2kpf;1mc -bot=6ud;xx -bottom=6ud;xu -bowtie=6vc;zi -boxDL=7dj;141 -boxDR=7dg;13y -boxDl=7di;140 -boxDr=7df;13x -boxH=7dc;13u -boxHD=7dy;14g -boxHU=7e1;14j -boxHd=7dw;14e -boxHu=7dz;14h -boxUL=7dp;147 -boxUR=7dm;144 -boxUl=7do;146 -boxUr=7dl;143 -boxV=7dd;13v -boxVH=7e4;14m -boxVL=7dv;14d -boxVR=7ds;14a -boxVh=7e3;14l -boxVl=7du;14c -boxVr=7dr;149 -boxbox=895;1bw -boxdL=7dh;13z -boxdR=7de;13w -boxdl=7bk;13m -boxdr=7bg;13l -boxh=7b4;13j -boxhD=7dx;14f -boxhU=7e0;14i -boxhd=7cc;13r -boxhu=7ck;13s -boxminus=6u7;xi -boxplus=6u6;xg -boxtimes=6u8;xk -boxuL=7dn;145 -boxuR=7dk;142 -boxul=7bs;13o -boxur=7bo;13n -boxv=7b6;13k -boxvH=7e2;14k -boxvL=7dt;14b -boxvR=7dq;148 -boxvh=7cs;13t -boxvl=7c4;13q -boxvr=7bw;13p -bprime=6d1;ej -breve=k8;83 -brvbar=4m;1k -bscr=2kl3;1jv -bsemi=6dr;er -bsim=6rh;qd -bsime=6vh;zq -bsol=2k;x -bsolb=891;1bv -bsolhsub=7uw;16r -bull=6ci;e9 -bullet=6ci;e8 -bump=6ry;rp -bumpE=8fi;1gu -bumpe=6rz;ru -bumpeq=6rz;rt -cacute=7b;4p -cap=6qx;pa -capand=8ck;1dq -capbrcup=8cp;1dv -capcap=8cr;1dx -capcup=8cn;1dt -capdot=8cg;1dn -caps=6qx,1e68;p9 -caret=6dd;eo -caron=jr;81 -ccaps=8ct;1dz -ccaron=7h;4v -ccedil=6f;3s -ccirc=7d;4r -ccups=8cs;1dy -ccupssm=8cw;1e0 -cdot=7f;4t -cedil=54;2f -cemptyv=88i;1bf -cent=4i;1g -centerdot=53;2c -cfr=2ko0;1l4 -chcy=uf;ce -check=7pv;16j -checkmark=7pv;16i -chi=qv;9s -cir=7gr;15q -cirE=88z;1bt -circ=jq;7z -circeq=6s7;sc -circlearrowleft=6nu;k6 -circlearrowright=6nv;k8 -circledR=4u;1w -circledS=79k;13g -circledast=6u3;xc -circledcirc=6u2;xa -circleddash=6u5;xe -cire=6s7;sd -cirfnint=8b4;1cq -cirmid=8hb;1j0 -cirscir=88y;1bs -clubs=7kz;168 -clubsuit=7kz;167 -colon=1m;j -colone=6s4;s7 -coloneq=6s4;s5 -comma=18;g -commat=1s;u -comp=6pt;mv -compfn=6qg;o7 -complement=6pt;mu -complexes=6iq;f6 -cong=6rp;qz -congdot=8dp;1ef -conint=6r2;pj -copf=2kpg;1md -coprod=6q8;nr -copy=4p;1r -copysr=6jb;fz -crarr=6np;k1 -cross=7pz;16k -cscr=2kl4;1jw -csub=8gf;1id -csube=8gh;1if -csup=8gg;1ie -csupe=8gi;1ig -ctdot=6wf;11g -cudarrl=854;18x -cudarrr=851;18u -cuepr=6vy;10m -cuesc=6vz;10o -cularr=6nq;k3 -cularrp=859;190 -cup=6qy;pc -cupbrcap=8co;1du -cupcap=8cm;1ds -cupcup=8cq;1dw -cupdot=6tp;we -cupor=8cl;1dr -cups=6qy,1e68;pb -curarr=6nr;k5 -curarrm=858;18z -curlyeqprec=6vy;10l -curlyeqsucc=6vz;10n -curlyvee=6vi;zr -curlywedge=6vj;zt -curren=4k;1i -curvearrowleft=6nq;k2 -curvearrowright=6nr;k4 -cuvee=6vi;zs -cuwed=6vj;zu -cwconint=6r6;pq -cwint=6r5;po -cylcty=6y5;12u -dArr=6oj;m2 -dHar=86d;19t -dagger=6cg;e5 -daleth=6k8;h7 -darr=6mr;ia -dash=6c0;dl -dashv=6ub;xr -dbkarow=83z;180 -dblac=kd;8b -dcaron=7j;4x -dcy=tw;bv -dd=6km;hb -ddagger=6ch;e6 -ddarr=6oa;ld -ddotseq=8dz;1ep -deg=4w;21 -delta=qc;95 -demptyv=88h;1be -dfisht=873;1aj -dfr=2ko1;1l5 -dharl=6o3;kx -dharr=6o2;ku -diam=6v8;zc -diamond=6v8;zb -diamondsuit=7l2;16b -diams=7l2;16c -die=4o;1o -digamma=rh;a6 -disin=6wi;11j -div=6v;49 -divide=6v;48 -divideontimes=6vb;zg -divonx=6vb;zh -djcy=uq;co -dlcorn=6xq;12n -dlcrop=6x9;12a -dollar=10;6 -dopf=2kph;1me -dot=k9;85 -doteq=6s0;rx -doteqdot=6s1;rz -dotminus=6rc;q2 -dotplus=6qc;ny -dotsquare=6u9;xm -doublebarwedge=6x2;11z -downarrow=6mr;i9 -downdownarrows=6oa;lc -downharpoonleft=6o3;kv -downharpoonright=6o2;ks -drbkarow=840;182 -drcorn=6xr;12p -drcrop=6x8;129 -dscr=2kl5;1jx -dscy=ut;cr -dsol=8ae;1cc -dstrok=7l;4z -dtdot=6wh;11i -dtri=7gf;15j -dtrif=7ge;15h -duarr=6ph;mo -duhar=86n;1a5 -dwangle=886;1b3 -dzcy=v3;d0 -dzigrarr=7wf;17r -eDDot=8dz;1eq -eDot=6s1;s0 -eacute=6h;3u -easter=8dq;1eg -ecaron=7v;57 -ecir=6s6;sb -ecirc=6i;3v -ecolon=6s5;s9 -ecy=ul;ck -edot=7r;53 -ee=6kn;he -efDot=6s2;s2 -efr=2ko2;1l6 -eg=8ey;1g9 -egrave=6g;3t -egs=8eu;1g5 -egsdot=8ew;1g7 -el=8ex;1g8 -elinters=73b;13e -ell=6j7;fv -els=8et;1g3 -elsdot=8ev;1g6 -emacr=7n;51 -empty=6px;n7 -emptyset=6px;n5 -emptyv=6px;n6 -emsp=6bn;d2 -emsp13=6bo;d3 -emsp14=6bp;d4 -eng=97;6h -ensp=6bm;d1 -eogon=7t;55 -eopf=2kpi;1mf -epar=6vp;103 -eparsl=89v;1c6 -eplus=8dt;1ek -epsi=qd;97 -epsilon=qd;96 -epsiv=s5;ae -eqcirc=6s6;sa -eqcolon=6s5;s8 -eqsim=6rm;qq -eqslantgtr=8eu;1g4 -eqslantless=8et;1g2 -equals=1p;p -equest=6sf;sj -equiv=6sh;so -equivDD=8e0;1er -eqvparsl=89x;1c8 -erDot=6s3;s4 -erarr=86p;1a7 -escr=6jz;gs -esdot=6s0;ry -esim=6rm;qr -eta=qf;99 -eth=6o;41 -euml=6j;3w -euro=6gc;f2 -excl=x;2 -exist=6pv;n0 -expectation=6k0;gt -exponentiale=6kn;hd -fallingdotseq=6s2;s1 -fcy=uc;cb -female=7k0;163 -ffilig=1dkz;1ja -fflig=1dkw;1j7 -ffllig=1dl0;1jb -ffr=2ko3;1l7 -filig=1dkx;1j8 -fjlig=2u,2y;15 -flat=7l9;16e -fllig=1dky;1j9 -fltns=7g1;153 -fnof=b6;7v -fopf=2kpj;1mg -forall=6ps;mt -fork=6vo;102 -forkv=8gp;1in -fpartint=8b1;1cp -frac12=59;2k -frac13=6kz;hh -frac14=58;2j -frac15=6l1;hj -frac16=6l5;hn -frac18=6l7;hp -frac23=6l0;hi -frac25=6l2;hk -frac34=5a;2m -frac35=6l3;hl -frac38=6l8;hq -frac45=6l4;hm -frac56=6l6;ho -frac58=6l9;hr -frac78=6la;hs -frasl=6dg;eq -frown=6xu;12r -fscr=2kl7;1jy -gE=6sn;t8 -gEl=8ek;1ft -gacute=dx;7x -gamma=qb;94 -gammad=rh;a7 -gap=8ee;1fh -gbreve=7z;5b -gcirc=7x;59 -gcy=tv;bu -gdot=81;5d -ge=6sl;sx -gel=6vv;10k -geq=6sl;sw -geqq=6sn;t7 -geqslant=8e6;1f6 -ges=8e6;1f7 -gescc=8fd;1gn -gesdot=8e8;1f9 -gesdoto=8ea;1fb -gesdotol=8ec;1fd -gesl=6vv,1e68;10h -gesles=8es;1g1 -gfr=2ko4;1l8 -gg=6sr;ts -ggg=6vt;10b -gimel=6k7;h6 -gjcy=ur;cp -gl=6t3;un -glE=8eq;1fz -gla=8f9;1gj -glj=8f8;1gi -gnE=6sp;tg -gnap=8ei;1fp -gnapprox=8ei;1fo -gne=8eg;1fl -gneq=8eg;1fk -gneqq=6sp;tf -gnsim=6w7;10y -gopf=2kpk;1mh -grave=2o;14 -gscr=6iy;f9 -gsim=6sz;ud -gsime=8em;1fv -gsiml=8eo;1fx -gt=1q;s -gtcc=8fb;1gl -gtcir=8e2;1et -gtdot=6vr;107 -gtlPar=87p;1aw -gtquest=8e4;1ev -gtrapprox=8ee;1fg -gtrarr=86w;1ad -gtrdot=6vr;106 -gtreqless=6vv;10j -gtreqqless=8ek;1fs -gtrless=6t3;um -gtrsim=6sz;uc -gvertneqq=6sp,1e68;td -gvnE=6sp,1e68;te -hArr=6ok;m5 -hairsp=6bu;da -half=59;2l -hamilt=6iz;fb -hardcy=ui;ch -harr=6ms;id -harrcir=85k;192 -harrw=6nh;js -hbar=6j3;fl -hcirc=85;5g -hearts=7l1;16a -heartsuit=7l1;169 -hellip=6cm;eb -hercon=6ux;yr -hfr=2ko5;1l9 -hksearow=84l;18i -hkswarow=84m;18k -hoarr=6pr;mr -homtht=6rf;q5 -hookleftarrow=6nd;jj -hookrightarrow=6ne;jl -hopf=2kpl;1mi -horbar=6c5;do -hscr=2kl9;1jz -hslash=6j3;fi -hstrok=87;5i -hybull=6df;ep -hyphen=6c0;dk -iacute=6l;3y -ic=6eb;f1 -icirc=6m;3z -icy=u0;bz -iecy=tx;bw -iexcl=4h;1f -iff=6ok;m6 -ifr=2ko6;1la -igrave=6k;3x -ii=6ko;hg -iiiint=8b0;1cn -iiint=6r1;pg -iinfin=89o;1c3 -iiota=6jt;gm -ijlig=8j;5t -imacr=8b;5m -image=6j5;fp -imagline=6j4;fm -imagpart=6j5;fo -imath=8h;5r -imof=6uv;yo -imped=c5;7w -in=6q0;nd -incare=6it;f8 -infin=6qm;of -infintie=89p;1c4 -inodot=8h;5q -int=6qz;pe -intcal=6uy;yt -integers=6jo;gh -intercal=6uy;ys -intlarhk=8bb;1cx -intprod=8cc;1dk -iocy=up;cn -iogon=8f;5o -iopf=2kpm;1mj -iota=qh;9b -iprod=8cc;1dl -iquest=5b;2n -iscr=2kla;1k0 -isin=6q0;nc -isinE=6wp;11r -isindot=6wl;11n -isins=6wk;11l -isinsv=6wj;11k -isinv=6q0;nb -it=6ea;ez -itilde=89;5k -iukcy=uu;cs -iuml=6n;40 -jcirc=8l;5v -jcy=u1;c0 -jfr=2ko7;1lb -jmath=fr;7y -jopf=2kpn;1mk -jscr=2klb;1k1 -jsercy=uw;cu -jukcy=us;cq -kappa=qi;9c -kappav=s0;a9 -kcedil=8n;5x -kcy=u2;c1 -kfr=2ko8;1lc -kgreen=8o;5y -khcy=ud;cc -kjcy=v0;cy -kopf=2kpo;1ml -kscr=2klc;1k2 -lAarr=6oq;mf -lArr=6og;ls -lAtail=84b;18a -lBarr=83y;17z -lE=6sm;t2 -lEg=8ej;1fr -lHar=86a;19q -lacute=8q;60 -laemptyv=88k;1bh -lagran=6j6;ft -lambda=qj;9d -lang=7vs;16z -langd=87l;1as -langle=7vs;16y -lap=8ed;1ff -laquo=4r;1t -larr=6mo;hx -larrb=6p0;mk -larrbfs=84f;18e -larrfs=84d;18c -larrhk=6nd;jk -larrlp=6nf;jo -larrpl=855;18y -larrsim=86r;1a9 -larrtl=6n6;j7 -lat=8ff;1gp -latail=849;188 -late=8fh;1gt -lates=8fh,1e68;1gs -lbarr=83w;17w -lbbrk=7si;16p -lbrace=3f;16 -lbrack=2j;v -lbrke=87f;1am -lbrksld=87j;1aq -lbrkslu=87h;1ao -lcaron=8u;64 -lcedil=8s;62 -lceil=6x4;122 -lcub=3f;17 -lcy=u3;c2 -ldca=852;18v -ldquo=6cc;dz -ldquor=6ce;e3 -ldrdhar=86f;19v -ldrushar=85n;195 -ldsh=6nm;jz -le=6sk;st -leftarrow=6mo;hv -leftarrowtail=6n6;j6 -leftharpoondown=6nx;kd -leftharpoonup=6nw;ka -leftleftarrows=6o7;l6 -leftrightarrow=6ms;ic -leftrightarrows=6o6;l4 -leftrightharpoons=6ob;lf -leftrightsquigarrow=6nh;jr -leftthreetimes=6vf;zl -leg=6vu;10g -leq=6sk;ss -leqq=6sm;t1 -leqslant=8e5;1f0 -les=8e5;1f1 -lescc=8fc;1gm -lesdot=8e7;1f8 -lesdoto=8e9;1fa -lesdotor=8eb;1fc -lesg=6vu,1e68;10d -lesges=8er;1g0 -lessapprox=8ed;1fe -lessdot=6vq;104 -lesseqgtr=6vu;10f -lesseqqgtr=8ej;1fq -lessgtr=6t2;uj -lesssim=6sy;u9 -lfisht=870;1ag -lfloor=6x6;126 -lfr=2ko9;1ld -lg=6t2;uk -lgE=8ep;1fy -lhard=6nx;kf -lharu=6nw;kc -lharul=86i;19y -lhblk=7es;14o -ljcy=ux;cv -ll=6sq;tm -llarr=6o7;l7 -llcorner=6xq;12m -llhard=86j;19z -lltri=7i2;15w -lmidot=8w;66 -lmoust=71s;131 -lmoustache=71s;130 -lnE=6so;tc -lnap=8eh;1fn -lnapprox=8eh;1fm -lne=8ef;1fj -lneq=8ef;1fi -lneqq=6so;tb -lnsim=6w6;10x -loang=7vw;175 -loarr=6pp;mp -lobrk=7vq;16u -longleftarrow=7w5;178 -longleftrightarrow=7w7;17e -longmapsto=7wc;17p -longrightarrow=7w6;17b -looparrowleft=6nf;jn -looparrowright=6ng;jp -lopar=879;1ak -lopf=2kpp;1mm -loplus=8bx;1d6 -lotimes=8c4;1dc -lowast=6qf;o5 -lowbar=2n;12 -loz=7gq;15p -lozenge=7gq;15o -lozf=8a3;1ca -lpar=14;b -lparlt=87n;1au -lrarr=6o6;l5 -lrcorner=6xr;12o -lrhar=6ob;lg -lrhard=86l;1a1 -lrm=6by;di -lrtri=6v3;yx -lsaquo=6d5;ek -lscr=2kld;1k3 -lsh=6nk;jw -lsim=6sy;ua -lsime=8el;1fu -lsimg=8en;1fw -lsqb=2j;w -lsquo=6c8;ds -lsquor=6ca;dw -lstrok=8y;68 -lt=1o;n -ltcc=8fa;1gk -ltcir=8e1;1es -ltdot=6vq;105 -lthree=6vf;zm -ltimes=6vd;zj -ltlarr=86u;1ac -ltquest=8e3;1eu -ltrPar=87q;1ax -ltri=7gj;15n -ltrie=6us;yi -ltrif=7gi;15l -lurdshar=85m;194 -luruhar=86e;19u -lvertneqq=6so,1e68;t9 -lvnE=6so,1e68;ta -mDDot=6re;q4 -macr=4v;20 -male=7k2;164 -malt=7q8;16m -maltese=7q8;16l -map=6na;jg -mapsto=6na;jf -mapstodown=6nb;ji -mapstoleft=6n8;jb -mapstoup=6n9;jd -marker=7fy;152 -mcomma=8bt;1d4 -mcy=u4;c3 -mdash=6c4;dn -measuredangle=6qp;ok -mfr=2koa;1le -mho=6jr;gj -micro=51;29 -mid=6qr;oq -midast=16;d -midcir=8hc;1j1 -middot=53;2d -minus=6qa;nu -minusb=6u7;xj -minusd=6rc;q3 -minusdu=8bu;1d5 -mlcp=8gr;1ip -mldr=6cm;ec -mnplus=6qb;nw -models=6uf;xy -mopf=2kpq;1mn -mp=6qb;nx -mscr=2kle;1k4 -mstpos=6ri;qf -mu=qk;9e -multimap=6uw;yp -mumap=6uw;yq -nGg=6vt,mw;10a -nGt=6sr,6he;tp -nGtv=6sr,mw;to -nLeftarrow=6od;lk -nLeftrightarrow=6oe;lm -nLl=6vs,mw;108 -nLt=6sq,6he;tj -nLtv=6sq,mw;ti -nRightarrow=6of;lo -nVDash=6un;y7 -nVdash=6um;y6 -nabla=6pz;n8 -nacute=90;6a -nang=6qo,6he;oh -nap=6rt;rb -napE=8ds,mw;1ei -napid=6rv,mw;re -napos=95;6f -napprox=6rt;ra -natur=7la;16g -natural=7la;16f -naturals=6j9;fw -nbsp=4g;1e -nbump=6ry,mw;rm -nbumpe=6rz,mw;rr -ncap=8cj;1dp -ncaron=94;6e -ncedil=92;6c -ncong=6rr;r2 -ncongdot=8dp,mw;1ee -ncup=8ci;1do -ncy=u5;c4 -ndash=6c3;dm -ne=6sg;sl -neArr=6on;mb -nearhk=84k;18h -nearr=6mv;im -nearrow=6mv;il -nedot=6s0,mw;rv -nequiv=6si;sq -nesear=84o;18n -nesim=6rm,mw;qo -nexist=6pw;n3 -nexists=6pw;n2 -nfr=2kob;1lf -ngE=6sn,mw;t4 -nge=6sx;u7 -ngeq=6sx;u6 -ngeqq=6sn,mw;t5 -ngeqslant=8e6,mw;1f3 -nges=8e6,mw;1f4 -ngsim=6t1;uh -ngt=6sv;u1 -ngtr=6sv;u0 -nhArr=6oe;ln -nharr=6ni;ju -nhpar=8he;1j3 -ni=6q3;nk -nis=6ws;11u -nisd=6wq;11s -niv=6q3;nj -njcy=uy;cw -nlArr=6od;ll -nlE=6sm,mw;sy -nlarr=6my;iu -nldr=6cl;ea -nle=6sw;u4 -nleftarrow=6my;it -nleftrightarrow=6ni;jt -nleq=6sw;u3 -nleqq=6sm,mw;sz -nleqslant=8e5,mw;1ex -nles=8e5,mw;1ey -nless=6su;tx -nlsim=6t0;uf -nlt=6su;ty -nltri=6wa;115 -nltrie=6wc;11b -nmid=6qs;ou -nopf=2kpr;1mo -not=4s;1u -notin=6q1;ng -notinE=6wp,mw;11q -notindot=6wl,mw;11m -notinva=6q1;nf -notinvb=6wn;11p -notinvc=6wm;11o -notni=6q4;nn -notniva=6q4;nm -notnivb=6wu;11w -notnivc=6wt;11v -npar=6qu;p4 -nparallel=6qu;p2 -nparsl=8hp,6hx;1j5 -npart=6pu,mw;mw -npolint=8b8;1cu -npr=6tc;vd -nprcue=6w0;10q -npre=8fj,mw;1gw -nprec=6tc;vc -npreceq=8fj,mw;1gx -nrArr=6of;lp -nrarr=6mz;iw -nrarrc=84z,mw;18s -nrarrw=6n1,mw;ix -nrightarrow=6mz;iv -nrtri=6wb;118 -nrtrie=6wd;11e -nsc=6td;vg -nsccue=6w1;10s -nsce=8fk,mw;1h2 -nscr=2klf;1k5 -nshortmid=6qs;os -nshortparallel=6qu;p1 -nsim=6rl;qm -nsime=6ro;qx -nsimeq=6ro;qw -nsmid=6qs;ot -nspar=6qu;p3 -nsqsube=6w2;10u -nsqsupe=6w3;10w -nsub=6tg;vs -nsubE=8g5,mw;1hv -nsube=6tk;w2 -nsubset=6te,6he;vi -nsubseteq=6tk;w1 -nsubseteqq=8g5,mw;1hw -nsucc=6td;vf -nsucceq=8fk,mw;1h3 -nsup=6th;vt -nsupE=8g6,mw;1hz -nsupe=6tl;w5 -nsupset=6tf,6he;vn -nsupseteq=6tl;w4 -nsupseteqq=8g6,mw;1i0 -ntgl=6t5;ur -ntilde=6p;42 -ntlg=6t4;up -ntriangleleft=6wa;114 -ntrianglelefteq=6wc;11a -ntriangleright=6wb;117 -ntrianglerighteq=6wd;11d -nu=ql;9f -num=z;5 -numero=6ja;fy -numsp=6br;d5 -nvDash=6ul;y5 -nvHarr=83o;17u -nvap=6rx,6he;ri -nvdash=6uk;y4 -nvge=6sl,6he;su -nvgt=1q,6he;q -nvinfin=89q;1c5 -nvlArr=83m;17s -nvle=6sk,6he;sr -nvlt=1o,6he;l -nvltrie=6us,6he;yf -nvrArr=83n;17t -nvrtrie=6ut,6he;yj -nvsim=6rg,6he;q6 -nwArr=6om;ma -nwarhk=84j;18g -nwarr=6mu;ij -nwarrow=6mu;ii -nwnear=84n;18m -oS=79k;13h -oacute=6r;44 -oast=6u3;xd -ocir=6u2;xb -ocirc=6s;45 -ocy=u6;c5 -odash=6u5;xf -odblac=9d;6l -odiv=8c8;1dg -odot=6u1;x9 -odsold=88s;1bn -oelig=9f;6n -ofcir=88v;1bp -ofr=2koc;1lg -ogon=kb;87 -ograve=6q;43 -ogt=88x;1br -ohbar=88l;1bi -ohm=q1;91 -oint=6r2;pk -olarr=6nu;k7 -olcir=88u;1bo -olcross=88r;1bm -oline=6da;en -olt=88w;1bq -omacr=99;6j -omega=qx;9u -omicron=qn;9h -omid=88m;1bj -ominus=6ty;x4 -oopf=2kps;1mp -opar=88n;1bk -operp=88p;1bl -oplus=6tx;x2 -or=6qw;p8 -orarr=6nv;k9 -ord=8d9;1ea -order=6k4;h1 -orderof=6k4;h0 -ordf=4q;1s -ordm=56;2h -origof=6uu;yn -oror=8d2;1e4 -orslope=8d3;1e5 -orv=8d7;1e8 -oscr=6k4;h2 -oslash=6w;4a -osol=6u0;x7 -otilde=6t;46 -otimes=6tz;x6 -otimesas=8c6;1de -ouml=6u;47 -ovbar=6yl;12x -par=6qt;oz -para=52;2a -parallel=6qt;ox -parsim=8hf;1j4 -parsl=8hp;1j6 -part=6pu;my -pcy=u7;c6 -percnt=11;7 -period=1a;h -permil=6cw;ed -perp=6ud;xw -pertenk=6cx;ee -pfr=2kod;1lh -phi=qu;9r -phiv=r9;a2 -phmmat=6k3;gy -phone=7im;162 -pi=qo;9i -pitchfork=6vo;101 -piv=ra;a4 -planck=6j3;fj -planckh=6j2;fh -plankv=6j3;fk -plus=17;f -plusacir=8bn;1cz -plusb=6u6;xh -pluscir=8bm;1cy -plusdo=6qc;nz -plusdu=8bp;1d1 -pluse=8du;1el -plusmn=4x;23 -plussim=8bq;1d2 -plustwo=8br;1d3 -pm=4x;24 -pointint=8b9;1cv -popf=2kpt;1mq -pound=4j;1h -pr=6t6;uu -prE=8fn;1h7 -prap=8fr;1he -prcue=6t8;v0 -pre=8fj;1h0 -prec=6t6;ut -precapprox=8fr;1hd -preccurlyeq=6t8;uz -preceq=8fj;1gz -precnapprox=8ft;1hh -precneqq=8fp;1h9 -precnsim=6w8;10z -precsim=6ta;v5 -prime=6cy;ef -primes=6jd;g2 -prnE=8fp;1ha -prnap=8ft;1hi -prnsim=6w8;110 -prod=6q7;np -profalar=6y6;12v -profline=6xe;12e -profsurf=6xf;12f -prop=6ql;oe -propto=6ql;oc -prsim=6ta;v6 -prurel=6uo;y8 -pscr=2klh;1k6 -psi=qw;9t -puncsp=6bs;d6 -qfr=2koe;1li -qint=8b0;1co -qopf=2kpu;1mr -qprime=6dz;es -qscr=2kli;1k7 -quaternions=6j1;ff -quatint=8ba;1cw -quest=1r;t -questeq=6sf;si -quot=y;4 -rAarr=6or;mh -rArr=6oi;lz -rAtail=84c;18b -rBarr=83z;181 -rHar=86c;19s -race=6rh,mp;qb -racute=9h;6p -radic=6qi;o8 -raemptyv=88j;1bg -rang=7vt;172 -rangd=87m;1at -range=885;1b2 -rangle=7vt;171 -raquo=57;2i -rarr=6mq;i6 -rarrap=86t;1ab -rarrb=6p1;mm -rarrbfs=84g;18f -rarrc=84z;18t -rarrfs=84e;18d -rarrhk=6ne;jm -rarrlp=6ng;jq -rarrpl=85h;191 -rarrsim=86s;1aa -rarrtl=6n7;j9 -rarrw=6n1;iz -ratail=84a;189 -ratio=6ra;pz -rationals=6je;g4 -rbarr=83x;17y -rbbrk=7sj;16q -rbrace=3h;1b -rbrack=2l;y -rbrke=87g;1an -rbrksld=87i;1ap -rbrkslu=87k;1ar -rcaron=9l;6t -rcedil=9j;6r -rceil=6x5;124 -rcub=3h;1c -rcy=u8;c7 -rdca=853;18w -rdldhar=86h;19x -rdquo=6cd;e2 -rdquor=6cd;e1 -rdsh=6nn;k0 -real=6jg;g9 -realine=6jf;g6 -realpart=6jg;g8 -reals=6jh;gc -rect=7fx;151 -reg=4u;1y -rfisht=871;1ah -rfloor=6x7;128 -rfr=2kof;1lj -rhard=6o1;kr -rharu=6o0;ko -rharul=86k;1a0 -rho=qp;9j -rhov=s1;ab -rightarrow=6mq;i4 -rightarrowtail=6n7;j8 -rightharpoondown=6o1;kp -rightharpoonup=6o0;km -rightleftarrows=6o4;kz -rightleftharpoons=6oc;lh -rightrightarrows=6o9;la -rightsquigarrow=6n1;iy -rightthreetimes=6vg;zn -ring=ka;86 -risingdotseq=6s3;s3 -rlarr=6o4;l0 -rlhar=6oc;lj -rlm=6bz;dj -rmoust=71t;133 -rmoustache=71t;132 -rnmid=8ha;1iz -roang=7vx;176 -roarr=6pq;mq -robrk=7vr;16w -ropar=87a;1al -ropf=2kpv;1ms -roplus=8by;1d7 -rotimes=8c5;1dd -rpar=15;c -rpargt=87o;1av -rppolint=8b6;1cs -rrarr=6o9;lb -rsaquo=6d6;el -rscr=2klj;1k8 -rsh=6nl;jy -rsqb=2l;z -rsquo=6c9;dv -rsquor=6c9;du -rthree=6vg;zo -rtimes=6ve;zk -rtri=7g9;15d -rtrie=6ut;ym -rtrif=7g8;15b -rtriltri=89a;1by -ruluhar=86g;19w -rx=6ji;ge -sacute=9n;6v -sbquo=6ca;dx -sc=6t7;ux -scE=8fo;1h8 -scap=8fs;1hg -scaron=9t;71 -sccue=6t9;v3 -sce=8fk;1h6 -scedil=9r;6z -scirc=9p;6x -scnE=8fq;1hc -scnap=8fu;1hk -scnsim=6w9;112 -scpolint=8b7;1ct -scsim=6tb;va -scy=u9;c8 -sdot=6v9;zd -sdotb=6u9;xn -sdote=8di;1ec -seArr=6oo;mc -searhk=84l;18j -searr=6mw;ip -searrow=6mw;io -sect=4n;1l -semi=1n;k -seswar=84p;18p -setminus=6qe;o2 -setmn=6qe;o4 -sext=7qu;16n -sfr=2kog;1lk -sfrown=6xu;12q -sharp=7lb;16h -shchcy=uh;cg -shcy=ug;cf -shortmid=6qr;oo -shortparallel=6qt;ow -shy=4t;1v -sigma=qr;9n -sigmaf=qq;9l -sigmav=qq;9m -sim=6rg;qa -simdot=8dm;1ed -sime=6rn;qu -simeq=6rn;qt -simg=8f2;1gb -simgE=8f4;1gd -siml=8f1;1ga -simlE=8f3;1gc -simne=6rq;r0 -simplus=8bo;1d0 -simrarr=86q;1a8 -slarr=6mo;hw -smallsetminus=6qe;o0 -smashp=8c3;1db -smeparsl=89w;1c7 -smid=6qr;op -smile=6xv;12t -smt=8fe;1go -smte=8fg;1gr -smtes=8fg,1e68;1gq -softcy=uk;cj -sol=1b;i -solb=890;1bu -solbar=6yn;12y -sopf=2kpw;1mt -spades=7kw;166 -spadesuit=7kw;165 -spar=6qt;oy -sqcap=6tv;wx -sqcaps=6tv,1e68;wv -sqcup=6tw;x0 -sqcups=6tw,1e68;wy -sqsub=6tr;wk -sqsube=6tt;wr -sqsubset=6tr;wj -sqsubseteq=6tt;wq -sqsup=6ts;wo -sqsupe=6tu;wu -sqsupset=6ts;wn -sqsupseteq=6tu;wt -squ=7fl;14v -square=7fl;14u -squarf=7fu;14y -squf=7fu;14z -srarr=6mq;i5 -sscr=2klk;1k9 -ssetmn=6qe;o3 -ssmile=6xv;12s -sstarf=6va;ze -star=7ie;161 -starf=7id;160 -straightepsilon=s5;ac -straightphi=r9;a0 -strns=4v;1z -sub=6te;vl -subE=8g5;1hy -subdot=8fx;1hn -sube=6ti;vw -subedot=8g3;1ht -submult=8g1;1hr -subnE=8gb;1i8 -subne=6tm;w9 -subplus=8fz;1hp -subrarr=86x;1ae -subset=6te;vk -subseteq=6ti;vv -subseteqq=8g5;1hx -subsetneq=6tm;w8 -subsetneqq=8gb;1i7 -subsim=8g7;1i3 -subsub=8gl;1ij -subsup=8gj;1ih -succ=6t7;uw -succapprox=8fs;1hf -succcurlyeq=6t9;v2 -succeq=8fk;1h5 -succnapprox=8fu;1hj -succneqq=8fq;1hb -succnsim=6w9;111 -succsim=6tb;v9 -sum=6q9;nt -sung=7l6;16d -sup=6tf;vr -sup1=55;2g -sup2=4y;25 -sup3=4z;26 -supE=8g6;1i2 -supdot=8fy;1ho -supdsub=8go;1im -supe=6tj;vz -supedot=8g4;1hu -suphsol=7ux;16s -suphsub=8gn;1il -suplarr=86z;1af -supmult=8g2;1hs -supnE=8gc;1ic -supne=6tn;wd -supplus=8g0;1hq -supset=6tf;vq -supseteq=6tj;vy -supseteqq=8g6;1i1 -supsetneq=6tn;wc -supsetneqq=8gc;1ib -supsim=8g8;1i4 -supsub=8gk;1ii -supsup=8gm;1ik -swArr=6op;md -swarhk=84m;18l -swarr=6mx;is -swarrow=6mx;ir -swnwar=84q;18r -szlig=67;3k -target=6xi;12h -tau=qs;9o -tbrk=71w;135 -tcaron=9x;75 -tcedil=9v;73 -tcy=ua;c9 -tdot=6hn;f4 -telrec=6xh;12g -tfr=2koh;1ll -there4=6r8;pv -therefore=6r8;pu -theta=qg;9a -thetasym=r5;9v -thetav=r5;9x -thickapprox=6rs;r3 -thicksim=6rg;q7 -thinsp=6bt;d8 -thkap=6rs;r7 -thksim=6rg;q8 -thorn=72;4g -tilde=kc;89 -times=5z;3c -timesb=6u8;xl -timesbar=8c1;1da -timesd=8c0;1d9 -tint=6r1;ph -toea=84o;18o -top=6uc;xt -topbot=6ye;12w -topcir=8hd;1j2 -topf=2kpx;1mu -topfork=8gq;1io -tosa=84p;18q -tprime=6d0;eh -trade=6jm;gg -triangle=7g5;158 -triangledown=7gf;15i -triangleleft=7gj;15m -trianglelefteq=6us;yh -triangleq=6sc;sg -triangleright=7g9;15c -trianglerighteq=6ut;yl -tridot=7ho;15r -trie=6sc;sh -triminus=8ca;1di -triplus=8c9;1dh -trisb=899;1bx -tritime=8cb;1dj -trpezium=736;13d -tscr=2kll;1ka -tscy=ue;cd -tshcy=uz;cx -tstrok=9z;77 -twixt=6ss;tu -twoheadleftarrow=6n2;j0 -twoheadrightarrow=6n4;j3 -uArr=6oh;lv -uHar=86b;19r -uacute=6y;4c -uarr=6mp;i1 -ubrcy=v2;cz -ubreve=a5;7d -ucirc=6z;4d -ucy=ub;ca -udarr=6o5;l2 -udblac=a9;7h -udhar=86m;1a3 -ufisht=872;1ai -ufr=2koi;1lm -ugrave=6x;4b -uharl=6nz;kl -uharr=6ny;ki -uhblk=7eo;14n -ulcorn=6xo;12j -ulcorner=6xo;12i -ulcrop=6xb;12c -ultri=7i0;15u -umacr=a3;7b -uml=4o;1p -uogon=ab;7j -uopf=2kpy;1mv -uparrow=6mp;i0 -updownarrow=6mt;if -upharpoonleft=6nz;kj -upharpoonright=6ny;kg -uplus=6tq;wg -upsi=qt;9q -upsih=r6;9y -upsilon=qt;9p -upuparrows=6o8;l8 -urcorn=6xp;12l -urcorner=6xp;12k -urcrop=6xa;12b -uring=a7;7f -urtri=7i1;15v -uscr=2klm;1kb -utdot=6wg;11h -utilde=a1;79 -utri=7g5;159 -utrif=7g4;157 -uuarr=6o8;l9 -uuml=70;4e -uwangle=887;1b4 -vArr=6ol;m9 -vBar=8h4;1iu -vBarv=8h5;1iv -vDash=6ug;y0 -vangrt=87w;1az -varepsilon=s5;ad -varkappa=s0;a8 -varnothing=6px;n4 -varphi=r9;a1 -varpi=ra;a3 -varpropto=6ql;ob -varr=6mt;ig -varrho=s1;aa -varsigma=qq;9k -varsubsetneq=6tm,1e68;w6 -varsubsetneqq=8gb,1e68;1i5 -varsupsetneq=6tn,1e68;wa -varsupsetneqq=8gc,1e68;1i9 -vartheta=r5;9w -vartriangleleft=6uq;y9 -vartriangleright=6ur;yc -vcy=tu;bt -vdash=6ua;xp -vee=6qw;p7 -veebar=6uz;yu -veeeq=6sa;sf -vellip=6we;11f -verbar=3g;19 -vert=3g;1a -vfr=2koj;1ln -vltri=6uq;yb -vnsub=6te,6he;vj -vnsup=6tf,6he;vo -vopf=2kpz;1mw -vprop=6ql;od -vrtri=6ur;ye -vscr=2kln;1kc -vsubnE=8gb,1e68;1i6 -vsubne=6tm,1e68;w7 -vsupnE=8gc,1e68;1ia -vsupne=6tn,1e68;wb -vzigzag=87u;1ay -wcirc=ad;7l -wedbar=8db;1eb -wedge=6qv;p5 -wedgeq=6s9;se -weierp=6jc;g0 -wfr=2kok;1lo -wopf=2kq0;1mx -wp=6jc;g1 -wr=6rk;qk -wreath=6rk;qj -wscr=2klo;1kd -xcap=6v6;z6 -xcirc=7hr;15t -xcup=6v7;z9 -xdtri=7gd;15f -xfr=2kol;1lp -xhArr=7wa;17o -xharr=7w7;17f -xi=qm;9g -xlArr=7w8;17i -xlarr=7w5;179 -xmap=7wc;17q -xnis=6wr;11t -xodot=8ao;1ce -xopf=2kq1;1my -xoplus=8ap;1cg -xotime=8aq;1ci -xrArr=7w9;17l -xrarr=7w6;17c -xscr=2klp;1ke -xsqcup=8au;1cm -xuplus=8as;1ck -xutri=7g3;155 -xvee=6v5;z2 -xwedge=6v4;yz -yacute=71;4f -yacy=un;cm -ycirc=af;7n -ycy=uj;ci -yen=4l;1j -yfr=2kom;1lq -yicy=uv;ct -yopf=2kq2;1mz -yscr=2klq;1kf -yucy=um;cl -yuml=73;4h -zacute=ai;7q -zcaron=am;7u -zcy=tz;by -zdot=ak;7s -zeetrf=6js;gk -zeta=qe;98 -zfr=2kon;1lr -zhcy=ty;bx -zigrarr=6ot;mi -zopf=2kq3;1n0 -zscr=2klr;1kg -zwj=6bx;dh -zwnj=6bw;dg diff --git a/src/main/java/org/jsoup/nodes/entities-xhtml.properties b/src/main/java/org/jsoup/nodes/entities-xhtml.properties deleted file mode 100644 index 6631d1aee7..0000000000 --- a/src/main/java/org/jsoup/nodes/entities-xhtml.properties +++ /dev/null @@ -1,4 +0,0 @@ -amp=12;1 -gt=1q;3 -lt=1o;2 -quot=y;0 diff --git a/src/test/java/org/jsoup/nodes/BuildEntities.java b/src/test/java/org/jsoup/nodes/BuildEntities.java index f20c0209e7..58bb1fce66 100644 --- a/src/test/java/org/jsoup/nodes/BuildEntities.java +++ b/src/test/java/org/jsoup/nodes/BuildEntities.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -20,8 +21,6 @@ * only to be complete. */ class BuildEntities { - private static final String projectDir = "/Users/jhy/projects/jsoup"; - public static void main(String[] args) throws IOException { String url = "https://www.w3.org/TR/2012/WD-html5-20121025/entities.json"; Connection.Response res = Jsoup.connect(url) @@ -68,20 +67,23 @@ public static void main(String[] args) throws IOException { } // now write them - persist("entities-full.properties", full); - persist("entities-base.properties", base); + persist("entities-full", full); + persist("entities-base", base); System.out.println("Full size: " + full.size() + ", base size: " + base.size()); } private static void persist(String name, ArrayList refs) throws IOException { - String base = projectDir + "/src/main/java/org/jsoup/nodes"; - File file = new File(base, name); + File file = Files.createTempFile(name, ".txt").toFile(); FileWriter writer = new FileWriter(file, false); + writer.append("static final String points = \""); for (CharacterRef ref : refs) { - writer.append(ref.toString()).append("\n"); + writer.append(ref.toString()).append('&'); } + writer.append("\";\n"); writer.close(); + + System.out.println("Wrote " + name + " to " + file.getAbsolutePath()); } From 5edd9aed61290b5c6123a0b498e902cc74783430 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Oct 2017 18:52:31 -0700 Subject: [PATCH 188/774] Improved handling of read timeout --- .../java/org/jsoup/helper/HttpConnection.java | 3 +-- .../org/jsoup/integration/ConnectTest.java | 2 +- .../jsoup/integration/servlets/SlowRider.java | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 7ee1cbc8db..393eeaffcd 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -67,7 +67,6 @@ public class HttpConnection implements Connection { private static final String MULTIPART_FORM_DATA = "multipart/form-data"; private static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; private static final int HTTP_TEMP_REDIR = 307; // http/1.1 temporary redirect, not in Java's set. - private static final int ReadTimeoutMillis = 800; // max time between reads - only throws if exceeds total request timeout private static final String DefaultUploadType = "application/octet-stream"; public static Connection connect(String url) { @@ -894,7 +893,7 @@ private static HttpURLConnection createConnection(Connection.Request req) throws conn.setRequestMethod(req.method().name()); conn.setInstanceFollowRedirects(false); // don't rely on native redirection support conn.setConnectTimeout(req.timeout()); - conn.setReadTimeout(ReadTimeoutMillis); + conn.setReadTimeout(req.timeout() / 2); // gets reduced after connection is made and status is read if (conn instanceof HttpsURLConnection) { if (!req.validateTLSCertificates()) { diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 3d55a0ac43..b2199292b3 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -256,7 +256,7 @@ public void run() { @Test public void slowReadOk() throws IOException { // make sure that a slow read that is under the request timeout is still OK Document doc = Jsoup.connect(SlowRider.Url) - .data(SlowRider.MaxTimeParam, "2000") // the reqest completes in 2 seconds + .data(SlowRider.MaxTimeParam, "2000") // the request completes in 2 seconds .get(); Element h1 = doc.selectFirst("h1"); diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java index 6c9e2a7157..29f2a77091 100644 --- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -13,12 +13,13 @@ */ public class SlowRider extends BaseServlet { public static final String Url = TestServer.map(SlowRider.class); - private static final int SleepTime = 1000; + private static final int SleepTime = 2000; public static final String MaxTimeParam = "maxTime"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + pause(1000); res.setContentType(TextHtml); res.setStatus(HttpServletResponse.SC_OK); PrintWriter w = res.getWriter(); @@ -37,11 +38,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser log("Remote connection lost"); break; } - try { - Thread.sleep(SleepTime); - } catch (InterruptedException e) { - break; - } + if (pause(SleepTime)) break; if (maxTime > 0 && System.currentTimeMillis() > startTime + maxTime) { w.println("

outatime

"); @@ -50,6 +47,15 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser } } + private static boolean pause(int sleepTime) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + return true; + } + return false; + } + // allow the servlet to run as a main program, for local test public static void main(String[] args) { TestServer.start(); From a58c8e679c00be9abea4925d3b96c16fd2cd9c64 Mon Sep 17 00:00:00 2001 From: krystiangorecki Date: Sat, 4 Nov 2017 18:25:50 +0100 Subject: [PATCH 189/774] removing last class should remove class attribute (#963) --- src/main/java/org/jsoup/nodes/Element.java | 6 +++++- src/test/java/org/jsoup/nodes/ElementTest.java | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 85d335af9c..671cf92bce 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1186,7 +1186,11 @@ public Set classNames() { */ public Element classNames(Set classNames) { Validate.notNull(classNames); - attributes().put("class", StringUtil.join(classNames, " ")); + if (classNames.isEmpty()) { + attributes().remove("class"); + } else { + attributes().put("class", StringUtil.join(classNames, " ")); + } return this; } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index d3d1163ac4..808492169e 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1235,4 +1235,15 @@ public void testNextElementSiblingAfterClone() { assertEquals(cloneExpect, cloneNextElementSibling.text()); assertEquals(cloneExpect, cloneNextSibling.text()); } + + @Test + public void testRemovingEmptyClassAttributeWhenLastClassRemoved() { + // https://github.com/jhy/jsoup/issues/947 + Document doc = Jsoup.parse(""); + Element img = doc.select("img").first(); + img.removeClass("one"); + img.removeClass("two"); + assertFalse(doc.body().html().contains("class=\"\"")); + } + } From 2d340db5b1ba48079b3bad81a5ffe7c9c7ac26e0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 4 Nov 2017 11:32:30 -0700 Subject: [PATCH 190/774] Added shallowClone() Fixes #900 --- CHANGES | 3 ++ src/main/java/org/jsoup/nodes/Element.java | 8 +++++- src/main/java/org/jsoup/nodes/Node.java | 13 ++++++++- .../java/org/jsoup/nodes/ElementTest.java | 28 +++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 1c3a5c4b03..bf86a22e1b 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,9 @@ jsoup changelog * Added support for multiple headers with the same name in Jsoup.Connect + * Added Element.shallowClone() and Node.shallowClone(), to allow cloning nodes without getting all their children. + + * Updated Element.text() and the :contains(text) selector to consider   character as spaces. * Updated Jsoup.connect().timeout() to implement a total connect + combined read timeout. Previously it specified diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 671cf92bce..f9746e56fc 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1398,13 +1398,19 @@ public Element clone() { return (Element) super.clone(); } + @Override + public Element shallowClone() { + // simpler than implementing a clone version with no child copy + return new Element(tag, baseUri, attributes); + } + @Override protected Element doClone(Node parent) { Element clone = (Element) super.doClone(parent); clone.attributes = attributes != null ? attributes.clone() : null; clone.baseUri = baseUri; clone.childNodes = new NodeList(clone, childNodes.size()); - clone.childNodes.addAll(childNodes); + clone.childNodes.addAll(childNodes); // the children then get iterated and cloned in Node.clone return clone; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 15d8f2bda6..37467a7c1c 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -639,7 +639,8 @@ public boolean hasSameValue(Object o) { * original node. *

* The cloned node may be adopted into another Document or node structure using {@link Element#appendChild(Node)}. - * @return stand-alone cloned node + * @return a stand-alone cloned node, including clones of any children + * @see #shallowClone() */ @Override public Node clone() { @@ -664,6 +665,16 @@ public Node clone() { return thisClone; } + /** + * Create a stand-alone, shallow copy of this node. None of its children (if any) will be cloned, and it will have + * no parent or sibling nodes. + * @return a single independent copy of this node + * @see #clone() + */ + public Node shallowClone() { + return doClone(null); + } + /* * Return a clone of the node using the given parent (which can be null). * Not a deep copy of children. diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 808492169e..91c095cf74 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -639,6 +639,34 @@ public class ElementTest { assertEquals("", copy.html()); } + @Test public void testShallowClone() { + String base = "http://example.com/"; + Document doc = Jsoup.parse("

One", base); + Element d = doc.selectFirst("div"); + Element p = doc.selectFirst("p"); + TextNode t = p.textNodes().get(0); + + Element d2 = d.shallowClone(); + Element p2 = p.shallowClone(); + TextNode t2 = (TextNode) t.shallowClone(); + + assertEquals(1, d.childNodeSize()); + assertEquals(0, d2.childNodeSize()); + + assertEquals(1, p.childNodeSize()); + assertEquals(0, p2.childNodeSize()); + assertEquals("", p2.text()); + + assertEquals("two", p2.className()); + assertEquals("One", t2.text()); + + d2.append("

Three"); + assertEquals(1, d2.childNodeSize()); + assertEquals("Three", d2.text()); + assertEquals("One", d.text()); + assertEquals(base, d2.baseUri()); + } + @Test public void testTagNameSet() { Document doc = Jsoup.parse("

Hello"); doc.select("i").first().tagName("em"); From 5dc4937611725462003f5cd87f41e56ee7829b65 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 4 Nov 2017 12:08:24 -0700 Subject: [PATCH 191/774] [maven-release-plugin] prepare release jsoup-1.11.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index dcc203454d..f68f66b8f7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.11.1-SNAPSHOT + 1.11.1 jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.11.1 Jonathan Hedley From 3993d72f9a75ec816730356e411db5a986025a2e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 4 Nov 2017 12:09:09 -0700 Subject: [PATCH 192/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f68f66b8f7..23ff5bed7c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.11.1 + 1.11.2-SNAPSHOT jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.11.1 + HEAD Jonathan Hedley From 1b38f80d623310e84c87b2d708ed0ab648dfafc5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 4 Nov 2017 18:11:21 -0700 Subject: [PATCH 193/774] Added sample from site --- .../java/org/jsoup/examples/Wikipedia.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/org/jsoup/examples/Wikipedia.java diff --git a/src/main/java/org/jsoup/examples/Wikipedia.java b/src/main/java/org/jsoup/examples/Wikipedia.java new file mode 100644 index 0000000000..085dc6bdd6 --- /dev/null +++ b/src/main/java/org/jsoup/examples/Wikipedia.java @@ -0,0 +1,27 @@ +package org.jsoup.examples; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; + +/** + * A simple example, used on the jsoup website. + */ +public class Wikipedia { + public static void main(String[] args) throws IOException { + Document doc = Jsoup.connect("http://en.wikipedia.org/").get(); + log(doc.title()); + + Elements newsHeadlines = doc.select("#mp-itn b a"); + for (Element headline : newsHeadlines) { + log("%s\n\t%s", headline.attr("title"), headline.absUrl("href")); + } + } + + private static void log(String msg, String... vals) { + System.out.println(String.format(msg, vals)); + } +} From adb7a15a1e2dd6742a25592602a6e8ee77b518b6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 6 Nov 2017 22:28:56 -0800 Subject: [PATCH 194/774] Start stack search at right spot Fixes a bug in the in-scope search, which was starting in the middle instead of the bottom when trying to limit the stack depth search. That meant that when generating implied end-tags, the stack wasn't getting shorter, so it tried to generate end tags for days, and ultimately threw a StackOverflowDetect. (Which is somewhat ironic, because the max depth was set to prevent a different STE... Le Sigh.) Fixes #966 --- CHANGES | 6 +++++- .../java/org/jsoup/parser/HtmlTreeBuilder.java | 16 ++++++++-------- .../org/jsoup/integration/UrlConnectTest.java | 9 +++++++++ .../java/org/jsoup/parser/HtmlParserTest.java | 13 +++++++++++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index bf86a22e1b..71488fb9ab 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog -*** Release 1.11.1 [PENDING] +*** Release 1.11.2 [PENDING] + * Fixed an issue where in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. + + +*** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 8eb3904ce7..b8cc1f03ad 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -464,13 +464,13 @@ private boolean inSpecificScope(String targetName, String[] baseTypes, String[] } private boolean inSpecificScope(String[] targetNames, String[] baseTypes, String[] extraTypes) { - int depth = stack.size() -1; - if (depth > MaxScopeSearchDepth) { - depth = MaxScopeSearchDepth; - } - for (int pos = depth; pos >= 0; pos--) { - Element el = stack.get(pos); - String elName = el.nodeName(); + // https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-the-specific-scope + final int bottom = stack.size() -1; + final int top = bottom > MaxScopeSearchDepth ? bottom - MaxScopeSearchDepth : 0; + // don't walk too far up the tree + + for (int pos = bottom; pos >= top; pos--) { + final String elName = stack.get(pos).nodeName(); if (inSorted(elName, targetNames)) return true; if (inSorted(elName, baseTypes)) @@ -478,7 +478,7 @@ private boolean inSpecificScope(String[] targetNames, String[] baseTypes, String if (extraTypes != null && inSorted(elName, extraTypes)) return false; } - Validate.fail("Should not be reachable"); + //Validate.fail("Should not be reachable"); // would end up false because hitting 'html' at root (basetypes) return false; } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 02e97ad342..6a4a20fb9d 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -658,4 +658,13 @@ public void inWildUtfRedirect2() throws IOException { assertTrue(System.currentTimeMillis() - start < 1000); } + @Test public void handles966() throws IOException { + // http://szshb.nxszs.gov.cn/ + // https://github.com/jhy/jsoup/issues/966 + + Document doc = Jsoup.connect("http://szshb.nxszs.gov.cn/").get(); + + assertEquals("石嘴山市环境保护局", doc.title()); + } + } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index afd2ee2786..d7d7bd850b 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1068,4 +1068,17 @@ public void testInvalidTableContents() throws IOException { assertTrue(template.childNodes().size() > 1); } } + + @Test public void testHandlesDeepSpans() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 200; i++) { + sb.append(""); + } + + sb.append("

One

"); + + Document doc = Jsoup.parse(sb.toString()); + assertEquals(200, doc.select("span").size()); + assertEquals(1, doc.select("p").size()); + } } From 1ece207d4d14336c405e9e3c2edee3f1cf88d14d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 10 Nov 2017 09:22:20 -0800 Subject: [PATCH 195/774] Handle empty buffer in attribute value read Fixes #967 --- CHANGES | 4 ++ .../org/jsoup/parser/CharacterReader.java | 2 +- .../java/org/jsoup/parser/TokeniserState.java | 9 +++-- .../java/org/jsoup/parser/TokeniserTest.java | 37 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/jsoup/parser/TokeniserTest.java diff --git a/CHANGES b/CHANGES index 71488fb9ab..aa51a956ca 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ jsoup changelog * Fixed an issue where in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. + * Fixed an issue when parsing large attribute values (e.g. image data values) where a character on a buffer boundary + read was dropped. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index c3638ae862..9ed41639b6 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -15,7 +15,7 @@ public final class CharacterReader { static final char EOF = (char) -1; private static final int maxStringCacheLen = 12; - private static final int maxBufferLen = 1024 * 32; + static final int maxBufferLen = 1024 * 32; // visible for testing private static final int readAheadLimit = (int) (maxBufferLen * 0.75); private final char[] charBuf; diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 3b02501abc..563312764c 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -753,7 +753,8 @@ void read(Tokeniser t, CharacterReader r) { t.eofError(this); t.transition(Data); break; - // no default, handled in consume to any above + default: // hit end of buffer in first read, still in attribute + t.tagPending.appendAttributeValue(c); } } }, @@ -785,7 +786,8 @@ void read(Tokeniser t, CharacterReader r) { t.eofError(this); t.transition(Data); break; - // no default, handled in consume to any above + default: // hit end of buffer in first read, still in attribute + t.tagPending.appendAttributeValue(c); } } }, @@ -831,7 +833,8 @@ void read(Tokeniser t, CharacterReader r) { t.error(this); t.tagPending.appendAttributeValue(c); break; - // no default, handled in consume to any above + default: // hit end of buffer in first read, still in attribute + t.tagPending.appendAttributeValue(c); } } diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java new file mode 100644 index 0000000000..d92f467f98 --- /dev/null +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -0,0 +1,37 @@ +package org.jsoup.parser; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class TokeniserTest { + @Test + public void bufferUpInAttributeVal() { + // https://github.com/jhy/jsoup/issues/967 + + // check each double, singlem, unquoted impls + String[] quotes = {"\"", "'", ""}; + for (String quote : quotes) { + String preamble = "\n"); + + String html = sb.toString(); + Document doc = Jsoup.parse(html); + String src = doc.select("img").attr("src"); + + assertTrue("Handles for quote " + quote, src.contains("X")); + assertTrue(src.contains(tail)); + } + } +} From e76922661ce7d97c1730052f60062d1e2eb328c4 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 10 Nov 2017 13:56:15 -0800 Subject: [PATCH 196/774] Correct other buffer underrun chances Related to #967 --- .../java/org/jsoup/parser/TokeniserState.java | 11 +++-- .../java/org/jsoup/parser/TokeniserTest.java | 46 ++++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 563312764c..8b4ce005da 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -148,7 +148,8 @@ void read(Tokeniser t, CharacterReader r) { String tagName = r.consumeTagName(); t.tagPending.appendTagName(tagName); - switch (r.consume()) { + char c = r.consume(); + switch (c) { case '\t': case '\n': case '\r': @@ -169,7 +170,9 @@ void read(Tokeniser t, CharacterReader r) { case eof: // should emit pending tag? t.eofError(this); t.transition(Data); - // no default, as covered with above consumeToAny + break; + default: // buffer underrun + t.tagPending.appendTagName(c); } } }, @@ -627,7 +630,9 @@ void read(Tokeniser t, CharacterReader r) { case '<': t.error(this); t.tagPending.appendAttributeName(c); - // no default, as covered in consumeToAny + break; + default: // buffer underrun + t.tagPending.appendAttributeName(c); } } }, diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index d92f467f98..5ea326a4bc 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -1,9 +1,15 @@ package org.jsoup.parser; import org.jsoup.Jsoup; +import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import org.junit.Test; +import static org.jsoup.parser.CharacterReader.maxBufferLen; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class TokeniserTest { @@ -18,7 +24,7 @@ public void bufferUpInAttributeVal() { String tail = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; StringBuilder sb = new StringBuilder(preamble); - final int charsToFillBuffer = CharacterReader.maxBufferLen - preamble.length(); + final int charsToFillBuffer = maxBufferLen - preamble.length(); for (int i = 0; i < charsToFillBuffer; i++) { sb.append('a'); } @@ -34,4 +40,42 @@ public void bufferUpInAttributeVal() { assertTrue(src.contains(tail)); } } + + @Test public void handleSuperLargeTagNames() { + // unlikely, but valid. so who knows. + + StringBuilder sb = new StringBuilder(maxBufferLen); + do { + sb.append("LargeTagName"); + } while (sb.length() < maxBufferLen); + String tag = sb.toString(); + String html = "<" + tag + ">One"; + + Document doc = Parser.htmlParser().settings(ParseSettings.preserveCase).parseInput(html, ""); + Elements els = doc.select(tag); + assertEquals(1, els.size()); + Element el = els.first(); + assertNotNull(el); + assertEquals("One", el.text()); + assertEquals(tag, el.tagName()); + } + + @Test public void handleSuperLargeAttributeName() { + StringBuilder sb = new StringBuilder(maxBufferLen); + do { + sb.append("LargAttributeName"); + } while (sb.length() < maxBufferLen); + String attrName = sb.toString(); + String html = "

One

"; + + Document doc = Jsoup.parse(html); + Elements els = doc.getElementsByAttribute(attrName); + assertEquals(1, els.size()); + Element el = els.first(); + assertNotNull(el); + assertEquals("One", el.text()); + Attribute attribute = el.attributes().asList().get(0); + assertEquals(attrName.toLowerCase(), attribute.getKey()); + assertEquals("foo", attribute.getValue()); + } } From d32321351ced83c7c2edff2abe4a8b2fa17a6bd7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 10 Nov 2017 15:29:57 -0800 Subject: [PATCH 197/774] Other buffer underrun tests --- .../java/org/jsoup/parser/TokeniserState.java | 5 +- .../java/org/jsoup/parser/TokeniserTest.java | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 8b4ce005da..94bcb0fd92 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -1603,8 +1603,9 @@ void read(Tokeniser t, CharacterReader r) { void read(Tokeniser t, CharacterReader r) { String data = r.consumeTo("]]>"); t.emit(data); - r.matchConsume("]]>"); - t.transition(Data); + if (r.matchConsume("]]>") || r.isEmpty()) { + t.transition(Data); + }// otherwise, buffer underrun, stay in data section } }; diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index 5ea326a4bc..a3da5fe5d4 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -2,8 +2,10 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import org.junit.Test; @@ -78,4 +80,78 @@ public void bufferUpInAttributeVal() { assertEquals(attrName.toLowerCase(), attribute.getKey()); assertEquals("foo", attribute.getValue()); } + + @Test public void handleLargeText() { + StringBuilder sb = new StringBuilder(maxBufferLen); + do { + sb.append("A Large Amount of Text"); + } while (sb.length() < maxBufferLen); + String text = sb.toString(); + String html = "

" + text + "

"; + + Document doc = Jsoup.parse(html); + Elements els = doc.select("p"); + assertEquals(1, els.size()); + Element el = els.first(); + + assertNotNull(el); + assertEquals(text, el.text()); + } + + @Test public void handleLargeComment() { + StringBuilder sb = new StringBuilder(maxBufferLen); + do { + sb.append("Quite a comment "); + } while (sb.length() < maxBufferLen); + String comment = sb.toString(); + String html = "

"; + + Document doc = Jsoup.parse(html); + Elements els = doc.select("p"); + assertEquals(1, els.size()); + Element el = els.first(); + + assertNotNull(el); + Comment child = (Comment) el.childNode(0); + assertEquals(" " + comment + " ", child.getData()); + } + + @Test public void handleLargeCdata() { + StringBuilder sb = new StringBuilder(maxBufferLen); + do { + sb.append("Quite a lot of CDATA <><><><>"); + } while (sb.length() < maxBufferLen); + String cdata = sb.toString(); + String html = "

"; + + Document doc = Jsoup.parse(html); + Elements els = doc.select("p"); + assertEquals(1, els.size()); + Element el = els.first(); + + assertNotNull(el); + TextNode child = (TextNode) el.childNode(0); + assertEquals(cdata, el.text()); + assertEquals(cdata, child.getWholeText()); + } + + @Test public void handleLargeTitle() { + StringBuilder sb = new StringBuilder(maxBufferLen); + do { + sb.append("Quite a long title"); + } while (sb.length() < maxBufferLen); + String title = sb.toString(); + String html = "" + title + ""; + + Document doc = Jsoup.parse(html); + Elements els = doc.select("title"); + assertEquals(1, els.size()); + Element el = els.first(); + + assertNotNull(el); + TextNode child = (TextNode) el.childNode(0); + assertEquals(title, el.text()); + assertEquals(title, child.getWholeText()); + assertEquals(title, doc.title()); + } } From c19acbb96c1c02deb310488677201683f6309708 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 12 Nov 2017 13:32:10 -0800 Subject: [PATCH 198/774] Support infinite timeouts Fixes #968 --- CHANGES | 3 +++ .../org/jsoup/internal/ConstrainableInputStream.java | 4 ++-- src/test/java/org/jsoup/integration/ConnectTest.java | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index aa51a956ca..6406e0d096 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ jsoup changelog read was dropped. + * Fixed an issue that prevented using infinite timeouts in Jsoup.Connection. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 7b3149f548..c043b421da 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -19,7 +19,7 @@ public final class ConstrainableInputStream extends BufferedInputStream { private final boolean capped; private final int maxSize; private long startTime; - private long timeout = -1; // optional max time of request + private long timeout = 0; // optional max time of request private int remaining; private boolean interrupted; @@ -111,7 +111,7 @@ public ConstrainableInputStream timeout(long startTimeNanos, long timeoutMillis) } private boolean expired() { - if (timeout == -1) + if (timeout == 0) return false; final long now = System.nanoTime(); diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index b2199292b3..911dbe4fae 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -263,6 +263,17 @@ public void run() { assertEquals("outatime", h1.text()); } + @Ignore + @Test public void infiniteReadSupported() throws IOException { + Document doc = Jsoup.connect(SlowRider.Url) + .timeout(0) + .data(SlowRider.MaxTimeParam, "2000") + .get(); + + Element h1 = doc.selectFirst("h1"); + assertEquals("outatime", h1.text()); + } + /** * Tests upload of content to a remote service. */ From 4d78e0316e1eae331bc0b460f367ce878688d6d2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 12 Nov 2017 14:23:10 -0800 Subject: [PATCH 199/774] Sort at write time, not start time --- .../java/org/jsoup/parser/TokeniserState.java | 18 ++++--------- .../org/jsoup/parser/TokenisetStateTest.java | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/jsoup/parser/TokenisetStateTest.java diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 94bcb0fd92..73e6de0e16 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -2,8 +2,6 @@ import org.jsoup.nodes.DocumentType; -import java.util.Arrays; - /** * States and transition activations for the Tokeniser. */ @@ -1613,22 +1611,16 @@ void read(Tokeniser t, CharacterReader r) { abstract void read(Tokeniser t, CharacterReader r); static final char nullChar = '\u0000'; - private static final char[] attributeSingleValueCharsSorted = new char[]{'\'', '&', nullChar}; - private static final char[] attributeDoubleValueCharsSorted = new char[]{'"', '&', nullChar}; - private static final char[] attributeNameCharsSorted = new char[]{'\t', '\n', '\r', '\f', ' ', '/', '=', '>', nullChar, '"', '\'', '<'}; - private static final char[] attributeValueUnquoted = new char[]{'\t', '\n', '\r', '\f', ' ', '&', '>', nullChar, '"', '\'', '<', '=', '`'}; + // char searches. must be sorted, used in inSorted. MUST update TokenisetStateTest if more arrays are added. + static final char[] attributeSingleValueCharsSorted = new char[]{nullChar, '&', '\''}; + static final char[] attributeDoubleValueCharsSorted = new char[]{nullChar, '"', '&'}; + static final char[] attributeNameCharsSorted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '\'', '/', '<', '=', '>'}; + static final char[] attributeValueUnquoted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '&', '\'', '<', '=', '>', '`'}; private static final char replacementChar = Tokeniser.replacementChar; private static final String replacementStr = String.valueOf(Tokeniser.replacementChar); private static final char eof = CharacterReader.EOF; - static { - Arrays.sort(attributeSingleValueCharsSorted); - Arrays.sort(attributeDoubleValueCharsSorted); - Arrays.sort(attributeNameCharsSorted); - Arrays.sort(attributeValueUnquoted); - } - /** * Handles RawtextEndTagName, ScriptDataEndTagName, and ScriptDataEscapedEndTagName. Same body impl, just * different else exit transitions. diff --git a/src/test/java/org/jsoup/parser/TokenisetStateTest.java b/src/test/java/org/jsoup/parser/TokenisetStateTest.java new file mode 100644 index 0000000000..e7ac1749a4 --- /dev/null +++ b/src/test/java/org/jsoup/parser/TokenisetStateTest.java @@ -0,0 +1,25 @@ +package org.jsoup.parser; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertArrayEquals; + +public class TokenisetStateTest { + @Test + public void ensureSearchArraysAreSorted() { + char[][] arrays = { + TokeniserState.attributeSingleValueCharsSorted, + TokeniserState.attributeDoubleValueCharsSorted, + TokeniserState.attributeNameCharsSorted, + TokeniserState.attributeValueUnquoted + }; + + for (char[] array : arrays) { + char[] copy = Arrays.copyOf(array, array.length); + Arrays.sort(array); + assertArrayEquals(array, copy); + } + } +} From f75f47397836ba2127f06c8390a87eada369f7cc Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 13 Nov 2017 00:57:36 +0100 Subject: [PATCH 200/774] Fix #429 Remove element from formData when element is removed --- .../java/org/jsoup/nodes/FormElement.java | 6 +++++ .../java/org/jsoup/nodes/FormElementTest.java | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index 6ef6b34a3b..f5c59d331c 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -46,6 +46,12 @@ public FormElement addElement(Element element) { return this; } + @Override + protected void removeChild(Node out) { + super.removeChild(out); + elements.remove(out); + } + /** * Prepare to submit this form. A Connection object is created with the request set up from the form values. You * can then set up other options (like user-agent, timeout, cookies), then execute it. diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index dc39c8a6df..3b20f0701c 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -144,4 +144,26 @@ public class FormElementTest { assertEquals("pass", data.get(1).key()); assertEquals("login", data.get(2).key()); } + + @Test public void removeFormElement() { + String html = "\n" + + " \n" + + " \n" + + " User:\n" + + " Password:\n" + + " \n" + + " \n" + + " \n" + + " "; + Document doc = Jsoup.parse(html); + FormElement form = (FormElement) doc.selectFirst("form"); + Element pass = form.selectFirst("input[name=pass]"); + pass.remove(); + + List data = form.formData(); + assertEquals(2, data.size()); + assertEquals("user", data.get(0).key()); + assertEquals("login", data.get(1).key()); + assertEquals(null, doc.selectFirst("input[name=pass]")); + } } From aeaf3c1a03fe4d5bfda4d63824a1f97dcec42e74 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 12 Nov 2017 16:02:31 -0800 Subject: [PATCH 201/774] Search further up the stack for

Fixes #722
---
 CHANGES                                        |  3 +++
 src/main/java/org/jsoup/nodes/Element.java     | 13 +++++++++----
 src/test/java/org/jsoup/nodes/ElementTest.java |  7 +++++++
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/CHANGES b/CHANGES
index 6406e0d096..cda5343901 100644
--- a/CHANGES
+++ b/CHANGES
@@ -11,6 +11,9 @@ jsoup changelog
   * Fixed an issue that prevented using infinite timeouts in Jsoup.Connection.
     
 
+  * Fixed an issue where whitespaces where not preserved in deeper stacks.
+    
+
 *** Release 1.11.1 [2017-Nov-06]
   * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are
     not used.
diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java
index f9746e56fc..ba1470bf19 100644
--- a/src/main/java/org/jsoup/nodes/Element.java
+++ b/src/main/java/org/jsoup/nodes/Element.java
@@ -1085,11 +1085,16 @@ private static void appendWhitespaceIfBr(Element element, StringBuilder accum) {
     }
 
     static boolean preserveWhitespace(Node node) {
-        // looks only at this element and one level up, to prevent recursion & needless stack searches
+        // looks only at this element and five levels up, to prevent recursion & needless stack searches
         if (node != null && node instanceof Element) {
-            Element element = (Element) node;
-            return element.tag.preserveWhitespace() ||
-                element.parent() != null && element.parent().tag.preserveWhitespace();
+            Element el = (Element) node;
+            int i = 0;
+            do {
+                if (el.tag.preserveWhitespace())
+                    return true;
+                el = el.parent();
+                i++;
+            } while (i < 6 && el != null);
         }
         return false;
     }
diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java
index 91c095cf74..546f3098cb 100644
--- a/src/test/java/org/jsoup/nodes/ElementTest.java
+++ b/src/test/java/org/jsoup/nodes/ElementTest.java
@@ -104,6 +104,13 @@ public class ElementTest {
         assertEquals("
code\n\ncode
", doc.body().html()); } + @Test public void testKeepsPreTextAtDepth() { + String h = "
code\n\ncode
"; + Document doc = Jsoup.parse(h); + assertEquals("code\n\ncode", doc.text()); + assertEquals("
code\n\ncode
", doc.body().html()); + } + @Test public void testBrHasSpace() { Document doc = Jsoup.parse("

Hello
there

"); assertEquals("Hello there", doc.text()); From 5fa76800671ff1d5477a383a0c3516d6fcc0a55a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 12 Nov 2017 16:13:28 -0800 Subject: [PATCH 202/774] Fixed an issue where were was not spelled correctly. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index cda5343901..c733416386 100644 --- a/CHANGES +++ b/CHANGES @@ -11,7 +11,7 @@ jsoup changelog * Fixed an issue that prevented using infinite timeouts in Jsoup.Connection. - * Fixed an issue where whitespaces where not preserved in deeper stacks. + * Fixed an issue where whitespaces were not preserved in deeper stacks. *** Release 1.11.1 [2017-Nov-06] From 1d7e6fa1c5fb974bdae7abeabbb1e5a48dc00967 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 12 Nov 2017 18:38:20 -0800 Subject: [PATCH 203/774] :matchText Fixes #550 --- CHANGES | 4 +++ .../org/jsoup/nodes/PseudoTextElement.java | 24 +++++++++++++++++ src/main/java/org/jsoup/select/Evaluator.java | 25 +++++++++++++++++ .../java/org/jsoup/select/QueryParser.java | 10 ++++--- src/main/java/org/jsoup/select/Selector.java | 1 + .../java/org/jsoup/select/SelectorTest.java | 27 +++++++++++++++++++ 6 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/PseudoTextElement.java diff --git a/CHANGES b/CHANGES index c733416386..756ba6291a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog *** Release 1.11.2 [PENDING] + * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. + This enables finding text that is only marked by a "br" tag, for example. + + * Fixed an issue where in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. diff --git a/src/main/java/org/jsoup/nodes/PseudoTextElement.java b/src/main/java/org/jsoup/nodes/PseudoTextElement.java new file mode 100644 index 0000000000..cacec3f011 --- /dev/null +++ b/src/main/java/org/jsoup/nodes/PseudoTextElement.java @@ -0,0 +1,24 @@ +package org.jsoup.nodes; + +import org.jsoup.parser.Tag; + +import java.io.IOException; + +/** + * Represents a {@link TextNode} as an {@link Element}, to enable text nodes to be selected with + * the {@link org.jsoup.select.Selector} {@code :matchText} syntax. + */ +public class PseudoTextElement extends Element { + + public PseudoTextElement(Tag tag, String baseUri, Attributes attributes) { + super(tag, baseUri, attributes); + } + + @Override + void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + } + + @Override + void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + } +} diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index b6670d4231..735606f22a 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -6,6 +6,8 @@ import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import org.jsoup.nodes.PseudoTextElement; +import org.jsoup.nodes.TextNode; import org.jsoup.nodes.XmlDeclaration; import java.util.List; @@ -750,4 +752,27 @@ public String toString() { return String.format(":matchesOwn(%s)", pattern); } } + + public static final class MatchText extends Evaluator { + + @Override + public boolean matches(Element root, Element element) { + if (element instanceof PseudoTextElement) + return true; + + List textNodes = element.textNodes(); + for (TextNode textNode : textNodes) { + PseudoTextElement pel = new PseudoTextElement( + org.jsoup.parser.Tag.valueOf(element.tagName()), element.baseUri(), element.attributes()); + textNode.replaceWith(pel); + pel.appendChild(textNode); + } + return false; + } + + @Override + public String toString() { + return ":matchText"; + } + } } diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index e7ee75b3de..99e51473c6 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -1,14 +1,14 @@ package org.jsoup.select; +import org.jsoup.helper.StringUtil; +import org.jsoup.helper.Validate; +import org.jsoup.parser.TokenQueue; + import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jsoup.helper.StringUtil; -import org.jsoup.helper.Validate; -import org.jsoup.parser.TokenQueue; - import static org.jsoup.internal.Normalizer.normalize; /** @@ -200,6 +200,8 @@ else if (tq.matchChomp(":empty")) evals.add(new Evaluator.IsEmpty()); else if (tq.matchChomp(":root")) evals.add(new Evaluator.IsRoot()); + else if (tq.matchChomp(":matchText")) + evals.add(new Evaluator.MatchText()); else // unhandled throw new Selector.SelectorParseException("Could not parse query '%s': unexpected token at '%s'", query, tq.remainder()); diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 8983ed4a5c..8b7aa47fe7 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -54,6 +54,7 @@ * :matchesOwn(regex)elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.td:matchesOwn(\\d+) finds table cells directly containing digits. div:matchesOwn((?i)login) finds divs containing the text, case insensitively. * :containsData(data)elements that contains the specified data. The contents of {@code script} and {@code style} elements, and {@code comment} nodes (etc) are considered data nodes, not text nodes. The search is case insensitive. The data may appear in the found element, or any of its descendants.script:contains(jsoup) finds script elements containing the data "jsoup". * The above may be combined in any order and with other selectors.light:contains(name):eq(0) + * :matchTexttreats text nodes as elements, and so allows you to match against and select text nodes.

Note that using this selector will modify the DOM, so you may want to {@code clone} your document before using.{@code p:matchText:firstChild} with input {@code

One
Two

} will return one {@link org.jsoup.nodes.PseudoTextElement} with text "{@code One}". *

Structural pseudo selectors

* :rootThe element that is the root of the document. In HTML, this is the html element:root * :nth-child(an+b)

elements that have an+b-1 siblings before it in the document tree, for any positive integer or zero value of n, and has a parent element. For values of a and b greater than zero, this effectively divides the element's children into groups of a elements (the last group taking the remainder), and selecting the bth element of each group. For example, this allows the selectors to address every other row in a table, and could be used to alternate the color of paragraph text in a cycle of four. The a and b values must be integers (positive, negative, or zero). The index of the first child of an element is 1.

diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index f0e36d43d3..06b53948fb 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -766,4 +766,31 @@ public void selectClassWithSpace() { Document doc = Jsoup.parse(html); assertEquals("One", doc.selectFirst("p, div").text()); } + + @Test public void textAsElements() { + String html = "

One
Two

"; + Document doc = Jsoup.parse(html); + String origHtml = doc.html(); + + Elements one = doc.select("p:matchText:first-child"); + assertEquals("One", one.first().text()); + + Elements two = doc.select("p:matchText:last-child"); + assertEquals("Two", two.first().text()); + + assertEquals(origHtml, doc.html()); + + assertEquals("Two", doc.select("p:matchText + br + *").text()); + } + + @Test public void splitOnBr() { + String html = "

One
Two
Three

"; + Document doc = Jsoup.parse(html); + + Elements els = doc.select("p:matchText"); + assertEquals(3, els.size()); + assertEquals("One", els.get(0).text()); + assertEquals("Two", els.get(1).text()); + assertEquals("Three", els.get(2).toString()); + } } From 5f0714329e2763d330460efee8ccd7f69acc8e7c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 13 Nov 2017 09:24:36 -0800 Subject: [PATCH 204/774] Some more :matchText tests --- .../java/org/jsoup/select/SelectorTest.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 06b53948fb..41649b7f08 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -767,7 +767,7 @@ public void selectClassWithSpace() { assertEquals("One", doc.selectFirst("p, div").text()); } - @Test public void textAsElements() { + @Test public void matchText() { String html = "

One
Two

"; Document doc = Jsoup.parse(html); String origHtml = doc.html(); @@ -793,4 +793,20 @@ public void selectClassWithSpace() { assertEquals("Two", els.get(1).text()); assertEquals("Three", els.get(2).toString()); } + + @Test public void matchTextAttributes() { + Document doc = Jsoup.parse("

One
Two

Three
Four"); + Elements els = doc.select("p.two:matchText:last-child"); + + assertEquals(1, els.size()); + assertEquals("Four", els.text()); + } + + @Test public void findBetweenSpan() { + Document doc = Jsoup.parse("

One Two Three"); + Elements els = doc.select("span ~ p:matchText"); // the Two becomes its own p, sibling of the span + + assertEquals(1, els.size()); + assertEquals("Two", els.text()); + } } From b8411990753314ed3b746d3402dec5a65ff6d603 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 14 Nov 2017 11:34:20 -0800 Subject: [PATCH 205/774] Don't OBE at EOF Fixes #972 --- CHANGES | 4 ++++ src/main/java/org/jsoup/parser/CharacterReader.java | 2 ++ .../java/org/jsoup/parser/CharacterReaderTest.java | 12 ++++++++++++ src/test/java/org/jsoup/parser/HtmlParserTest.java | 5 +++++ 4 files changed, 23 insertions(+) diff --git a/CHANGES b/CHANGES index 756ba6291a..47b0c2c84d 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,10 @@ jsoup changelog * Fixed an issue where whitespaces were not preserved in deeper stacks. + * Fixed an issue where an unterminated comment token at the end of the HTML input would cause an out of bounds + exception. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 9ed41639b6..8269e7949d 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -424,6 +424,8 @@ private static String cacheString(final char[] charBuf, final String[] stringCac // limit (no cache): if (count > maxStringCacheLen) return new String(charBuf, start, count); + if (count < 1) + return ""; // calculate hash: int hash = 0; diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 2a0084e58e..1325eb262d 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -256,5 +256,17 @@ public void empty() { assertEquals("Two", two); } + @Test + public void consumeToNonexistentEndWhenAtAnd() { + CharacterReader r = new CharacterReader("'); + assertEquals("", after); + + assertTrue(r.isEmpty()); + } + } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index d7d7bd850b..8e91a6a34f 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1081,4 +1081,9 @@ public void testInvalidTableContents() throws IOException { assertEquals(200, doc.select("span").size()); assertEquals(1, doc.select("p").size()); } + + @Test public void commentAtEnd() throws Exception { + Document doc = Jsoup.parse(" Date: Tue, 14 Nov 2017 12:39:20 -0800 Subject: [PATCH 206/774] NPE fix --- CHANGES | 3 +++ src/main/java/org/jsoup/internal/Normalizer.java | 2 +- src/test/java/org/jsoup/safety/CleanerTest.java | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 47b0c2c84d..8ae1720055 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,9 @@ jsoup changelog exception. + * Fixed an NPE issue in the Cleaner which would occur if an attribute value was missing. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/internal/Normalizer.java b/src/main/java/org/jsoup/internal/Normalizer.java index 0583694a01..d50433751e 100644 --- a/src/main/java/org/jsoup/internal/Normalizer.java +++ b/src/main/java/org/jsoup/internal/Normalizer.java @@ -8,7 +8,7 @@ public final class Normalizer { public static String lowerCase(final String input) { - return input.toLowerCase(Locale.ENGLISH); + return input != null ? input.toLowerCase(Locale.ENGLISH) : ""; } public static String normalize(final String input) { diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 50876d0539..9da0541134 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -298,4 +298,11 @@ public void bailsIfRemovingProtocolThatsNotSet() { String clean = Jsoup.clean(html, Whitelist.basic()); assertEquals("", clean); } + + @Test public void handlesAttributesWithNoValue() { + // https://github.com/jhy/jsoup/issues/973 + String clean = Jsoup.clean("Clean", Whitelist.basic()); + + assertEquals("Clean", clean); + } } From 3264eee7edb8eacb4da97155a7224712dceda9e0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 14 Nov 2017 15:51:51 -0800 Subject: [PATCH 207/774] Expanded sample code --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 345b718ed9..3432bed8ea 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,16 @@ jsoup is designed to deal with all varieties of HTML found in the wild; from pri See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API documentation](https://jsoup.org/apidocs/). ## Example -Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html) ([online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g)): +Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html) ([online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g), [full source](https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/examples/Wikipedia.java)): ```java Document doc = Jsoup.connect("http://en.wikipedia.org/").get(); +log(doc.title()); Elements newsHeadlines = doc.select("#mp-itn b a"); +for (Element headline : newsHeadlines) { + log("%s\n\t%s", + headline.attr("title"), headline.absUrl("href")); +} ``` ## Open source From e38af6a351cef738885cb61fed828d9c9f6ec5a1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 14 Nov 2017 21:38:38 -0800 Subject: [PATCH 208/774] Threadlocal encoder Fixes #970 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Document.java | 12 ++++++++++-- src/main/java/org/jsoup/nodes/Entities.java | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 8ae1720055..2cc1667f04 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,10 @@ jsoup changelog * Fixed an NPE issue in the Cleaner which would occur if an attribute value was missing. + * Fixed an issue where when serializing the same document in a multiple threads, on Android, with a character set that + is not ascii or UTF-8, an encoding exception could occur. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 2f1c03ff78..4944e358e3 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -371,7 +371,8 @@ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset; - CharsetEncoder encoder; // initialized by start of OuterHtmlVisitor and cleared at end + private ThreadLocal encoderThreadLocal = new ThreadLocal<>(); + //CharsetEncoder encoder; // initialized by start of OuterHtmlVisitor and cleared at end Entities.CoreCharset coreCharset; // fast encoders for ascii and utf8 private boolean prettyPrint = true; @@ -439,11 +440,18 @@ public OutputSettings charset(String charset) { } CharsetEncoder prepareEncoder() { - encoder = charset.newEncoder(); // created at start of OuterHtmlVisitor so each pass has own encoder, so OutputSettings can be shared among threads + //encoder = charset.newEncoder(); // created at start of OuterHtmlVisitor so each pass has own encoder, so OutputSettings can be shared among threads + CharsetEncoder encoder = charset.newEncoder(); + encoderThreadLocal.set(encoder); coreCharset = Entities.CoreCharset.byName(encoder.charset().name()); return encoder; } + CharsetEncoder encoder() { + CharsetEncoder encoder = encoderThreadLocal.get(); + return encoder != null ? encoder : prepareEncoder(); + } + /** * Get the document's current output syntax. * @return current syntax diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index ce98e7f1eb..bbf783f7ce 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -176,7 +176,7 @@ static void escape(Appendable accum, String string, Document.OutputSettings out, boolean lastWasWhite = false; boolean reachedNonWhite = false; final EscapeMode escapeMode = out.escapeMode(); - final CharsetEncoder encoder = out.encoder != null ? out.encoder : out.prepareEncoder(); + final CharsetEncoder encoder = out.encoder(); final CoreCharset coreCharset = out.coreCharset; // init in out.prepareEncoder() final int length = string.length(); From 6f9013beaf53ab0786c4587505d1ba1f76e5eb99 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Tue, 14 Nov 2017 21:47:41 -0800 Subject: [PATCH 209/774] Comment cleanup --- src/main/java/org/jsoup/nodes/Document.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 4944e358e3..b7143bbff5 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -371,8 +371,7 @@ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset; - private ThreadLocal encoderThreadLocal = new ThreadLocal<>(); - //CharsetEncoder encoder; // initialized by start of OuterHtmlVisitor and cleared at end + private ThreadLocal encoderThreadLocal = new ThreadLocal<>(); // initialized by start of OuterHtmlVisitor Entities.CoreCharset coreCharset; // fast encoders for ascii and utf8 private boolean prettyPrint = true; @@ -440,7 +439,7 @@ public OutputSettings charset(String charset) { } CharsetEncoder prepareEncoder() { - //encoder = charset.newEncoder(); // created at start of OuterHtmlVisitor so each pass has own encoder, so OutputSettings can be shared among threads + // created at start of OuterHtmlVisitor so each pass has own encoder, so OutputSettings can be shared among threads CharsetEncoder encoder = charset.newEncoder(); encoderThreadLocal.set(encoder); coreCharset = Entities.CoreCharset.byName(encoder.charset().name()); From d1f9c8ec636bfc813f2322e42c692303fa13fe91 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 16 Nov 2017 19:18:37 -0800 Subject: [PATCH 210/774] Changelog not cleanup --- CHANGES | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 2cc1667f04..1d783ef0da 100644 --- a/CHANGES +++ b/CHANGES @@ -5,28 +5,26 @@ jsoup changelog This enables finding text that is only marked by a "br" tag, for example. - * Fixed an issue where in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. + * Bugfix: in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. - * Fixed an issue when parsing large attribute values (e.g. image data values) where a character on a buffer boundary - read was dropped. + * Bugfix: when parsing attribute values that happened to cross a buffer boundary, a character was dropped. - * Fixed an issue that prevented using infinite timeouts in Jsoup.Connection. + * Bugfix: fixed an issue that prevented using infinite timeouts in Jsoup.Connection. - * Fixed an issue where whitespaces were not preserved in deeper stacks. + * Bugfix: whitespace preserving tags were not honoured when nested deeper than two levels deep. - * Fixed an issue where an unterminated comment token at the end of the HTML input would cause an out of bounds - exception. + * Bugfix: an unterminated comment token at the end of the HTML input would cause an out of bounds exception. - * Fixed an NPE issue in the Cleaner which would occur if an attribute value was missing. + * Bugfix: an NPE in the Cleaner which would occur if an attribute value was missing. - * Fixed an issue where when serializing the same document in a multiple threads, on Android, with a character set that - is not ascii or UTF-8, an encoding exception could occur. + * Bugfix: when serializing the same document in a multiple threads, on Android, with a character set that is not ascii + or UTF-8, an encoding exception could occur. *** Release 1.11.1 [2017-Nov-06] @@ -77,7 +75,7 @@ jsoup changelog * Added missing support for template tags in tables - * In Jsoup.connect file uploads, added the ability to set the uploaded files' mimetype. + * In Jsoup.connect file uploads, added the ability to set the uploaded files' mimetype. * Improved Node traversal, including less object creation, and partial and filtering traversor support. From 8c7414da71bc7c156f44638f44718c5f70b70460 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 16 Nov 2017 19:24:18 -0800 Subject: [PATCH 211/774] Changelog for #969 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 1d783ef0da..f814c9dbf6 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,9 @@ jsoup changelog or UTF-8, an encoding exception could occur. + * Bugfix: removing a form value from the DOM would not remove it from FormData. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. From 3475dc846d78e56422b3e6fdf36dfd4416f359f5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 18 Nov 2017 12:52:52 -0800 Subject: [PATCH 212/774] Maintain stack for namespaces Fixes #977 --- CHANGES | 3 ++ src/main/java/org/jsoup/helper/W3CDom.java | 10 ++++-- .../java/org/jsoup/helper/W3CDomTest.java | 36 +++++++++++++++++++ src/test/resources/htmltests/namespaces.xhtml | 9 ++++- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index f814c9dbf6..3d90c87dd6 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,9 @@ jsoup changelog * Bugfix: removing a form value from the DOM would not remove it from FormData. + * Bugfix: in the W3CDom transformer, siblings were incorrectly inheriting namespaces defined on previous siblings. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index ac71e59bce..81ac932499 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -19,6 +19,7 @@ import javax.xml.transform.stream.StreamResult; import java.io.StringWriter; import java.util.HashMap; +import java.util.Stack; /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, @@ -70,19 +71,21 @@ protected static class W3CBuilder implements NodeVisitor { private static final String xmlnsPrefix = "xmlns:"; private final Document doc; - private final HashMap namespaces = new HashMap<>(); // prefix => urn + private final Stack> namespacesStack = new Stack<>(); // stack of namespaces, prefix => urn private Element dest; public W3CBuilder(Document doc) { this.doc = doc; + this.namespacesStack.push(new HashMap()); } public void head(org.jsoup.nodes.Node source, int depth) { + namespacesStack.push(new HashMap<>(namespacesStack.peek())); // inherit from above on the stack if (source instanceof org.jsoup.nodes.Element) { org.jsoup.nodes.Element sourceEl = (org.jsoup.nodes.Element) source; String prefix = updateNamespaces(sourceEl); - String namespace = namespaces.get(prefix); + String namespace = namespacesStack.peek().get(prefix); Element el = doc.createElementNS(namespace, sourceEl.tagName()); copyAttributes(sourceEl, el); @@ -113,6 +116,7 @@ public void tail(org.jsoup.nodes.Node source, int depth) { if (source instanceof org.jsoup.nodes.Element && dest.getParentNode() instanceof Element) { dest = (Element) dest.getParentNode(); // undescend. cromulent. } + namespacesStack.pop(); } private void copyAttributes(org.jsoup.nodes.Node source, Element el) { @@ -141,7 +145,7 @@ private String updateNamespaces(org.jsoup.nodes.Element el) { } else { continue; } - namespaces.put(prefix, attr.getValue()); + namespacesStack.peek().put(prefix, attr.getValue()); } // get the element prefix if any diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 983816257e..f4d46d89ea 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -77,7 +77,14 @@ public void namespacePreservation() throws IOException { assertEquals("html", htmlEl.getLocalName()); assertEquals("html", htmlEl.getNodeName()); + // inherits default namespace + Node head = htmlEl.getFirstChild(); + assertEquals("http://www.w3.org/1999/xhtml", head.getNamespaceURI()); + assertEquals("head", head.getLocalName()); + assertEquals("head", head.getNodeName()); + Node epubTitle = htmlEl.getChildNodes().item(2).getChildNodes().item(3); + assertEquals("Check", epubTitle.getTextContent()); assertEquals("http://www.idpf.org/2007/ops", epubTitle.getNamespaceURI()); assertEquals("title", epubTitle.getLocalName()); assertEquals("epub:title", epubTitle.getNodeName()); @@ -86,6 +93,35 @@ public void namespacePreservation() throws IOException { assertEquals("urn:test", xSection.getNamespaceURI()); assertEquals("section", xSection.getLocalName()); assertEquals("x:section", xSection.getNodeName()); + + // https://github.com/jhy/jsoup/issues/977 + // does not keep last set namespace + Node svg = xSection.getNextSibling().getNextSibling(); + assertEquals("http://www.w3.org/2000/svg", svg.getNamespaceURI()); + assertEquals("svg", svg.getLocalName()); + assertEquals("svg", svg.getNodeName()); + + Node path = svg.getChildNodes().item(1); + assertEquals("http://www.w3.org/2000/svg", path.getNamespaceURI()); + assertEquals("path", path.getLocalName()); + assertEquals("path", path.getNodeName()); + + Node clip = path.getChildNodes().item(1); + assertEquals("http://example.com/clip", clip.getNamespaceURI()); + assertEquals("clip", clip.getLocalName()); + assertEquals("clip", clip.getNodeName()); + assertEquals("456", clip.getTextContent()); + + Node picture = svg.getNextSibling().getNextSibling(); + assertEquals("http://www.w3.org/1999/xhtml", picture.getNamespaceURI()); + assertEquals("picture", picture.getLocalName()); + assertEquals("picture", picture.getNodeName()); + + Node img = picture.getFirstChild(); + assertEquals("http://www.w3.org/1999/xhtml", img.getNamespaceURI()); + assertEquals("img", img.getLocalName()); + assertEquals("img", img.getNodeName()); + } @Test diff --git a/src/test/resources/htmltests/namespaces.xhtml b/src/test/resources/htmltests/namespaces.xhtml index 16bb1e4fc1..eeb5e4c5d6 100644 --- a/src/test/resources/htmltests/namespaces.xhtml +++ b/src/test/resources/htmltests/namespaces.xhtml @@ -18,6 +18,13 @@ Section Text. - <:foo>Test + + + 123 + 456 + + + + From c3f8caa7c16c08b803b0f34bfffdf9777c7e382c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 18 Nov 2017 13:38:01 -0800 Subject: [PATCH 213/774] Normalize invisibles in text() Fixes #978 --- CHANGES | 3 +++ src/main/java/org/jsoup/helper/StringUtil.java | 7 ++++++- src/test/java/org/jsoup/nodes/ElementTest.java | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3d90c87dd6..efff23705f 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ jsoup changelog * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. + + * Improvement: normalize invisible characters (like soft-hyphens) in Element.text(). + * Bugfix: in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index c9bf750b65..9d2e7443a1 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -128,6 +128,11 @@ public static boolean isActuallyWhitespace(int c){ // 160 is   (non-breaking space). Not in the spec but expected. } + public static boolean isInvisibleChar(int c) { + return Character.getType(c) == 16 && (c == 8203 || c == 8204 || c == 8205 || c == 173); + // zero width sp, zw non join, zw join, soft hyphen + } + /** * Normalise the whitespace within this string; multiple spaces collapse to a single, and all whitespace characters * (e.g. newline, tab) convert to a simple space @@ -160,7 +165,7 @@ public static void appendNormalisedWhitespace(StringBuilder accum, String string accum.append(' '); lastWasWhite = true; } - else { + else if (!isInvisibleChar(c)) { accum.appendCodePoint(c); lastWasWhite = false; reachedNonWhite = true; diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 546f3098cb..5225bff182 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1199,6 +1199,24 @@ public void testAppendTo() { Element matched = doc.select("p:contains(get what you want)").first(); assertEquals("p", matched.nodeName()); assertTrue(matched.is(":containsOwn(get what you want)")); + } + + @Test public void testNormalizesInvisiblesInText() { + // return Character.getType(c) == 16 && (c == 8203 || c == 8204 || c == 8205 || c == 173); + String escaped = "This­is​one‌long‍word"; + String decoded = "This\u00ADis\u200Bone\u200Clong\u200Dword"; // browser would not display those soft hyphens / other chars, so we don't want them in the text + + Document doc = Jsoup.parse("

" + escaped); + Element p = doc.select("p").first(); + doc.outputSettings().charset("ascii"); // so that the outer html is easier to see with escaped invisibles + assertEquals("Thisisonelongword", p.text()); // text is normalized + assertEquals("

" + escaped + "

", p.outerHtml()); // html / whole text keeps ­ etc; + assertEquals(decoded, p.textNodes().get(0).getWholeText()); + + Element matched = doc.select("p:contains(Thisisonelongword)").first(); // really just oneloneword, no invisibles + assertEquals("p", matched.nodeName()); + assertTrue(matched.is(":containsOwn(Thisisonelongword)")); + } @Test From f4c700b8cf147638eb09aa76a7ef4f2de70f8ea9 Mon Sep 17 00:00:00 2001 From: Hyun-Seok Oh Date: Mon, 20 Nov 2017 11:31:36 +0900 Subject: [PATCH 214/774] Added TokeniserState tests (#859) * Added TokeniserState tests --- .../org/jsoup/parser/TokeniserStateTest.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/test/java/org/jsoup/parser/TokeniserStateTest.java diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java new file mode 100644 index 0000000000..926c7716e3 --- /dev/null +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -0,0 +1,182 @@ +package org.jsoup.parser; + +import static org.junit.Assert.*; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; +import org.jsoup.select.Elements; +import org.junit.Test; + +public class TokeniserStateTest { + + final char[] whiteSpace = { '\t', '\n', '\r', '\f', ' ' }; + final char[] quote = { '\'', '"' }; + + @Test + public void testCharacterReferenceInRcdata() { + String body = ""; + Document doc = Jsoup.parse(body); + Elements els = doc.select("textarea"); + assertEquals("You&I", els.text()); + } + + @Test + public void testBeforeTagName() { + for (char c : whiteSpace) { + String body = String.format("test
", c); + Document doc = Jsoup.parse(body); + Elements els = doc.select("div"); + assertEquals("test", els.text()); + } + } + + @Test + public void testEndTagOpen() { + String body; + Document doc; + Elements els; + + body = "
hello worldhello world
"; + doc = Jsoup.parse(body); + els = doc.select("div"); + assertEquals("hello world", els.text()); + + body = "
fake
"; + doc = Jsoup.parse(body); + els = doc.select("div"); + assertEquals("fake", els.text()); + + body = "
fake"; + doc = Jsoup.parse(body); + els = doc.select("div"); + assertEquals("fake", els.text()); + } + + @Test + public void testRcdataLessthanSign() { + String body; + Document doc; + Elements els; + + body = ""; + doc = Jsoup.parse(body); + els = doc.select("textarea"); + assertEquals("", els.text()); + + body = ""; + doc = Jsoup.parse(body); + els = doc.select("textarea"); + assertEquals("hello worlddata", c); + Document doc = Jsoup.parse(body); + Elements els = doc.select("textarea"); + assertEquals("data", els.text()); + } + } + + @Test + public void testCommentEndCoverage() { + String html = "

Hello

"; + Document doc = Jsoup.parse(html); + + Element body = doc.body(); + Comment comment = (Comment) body.childNode(1); + assertEquals("
--! --- ", comment.getData()); + Element p = body.child(1); + TextNode text = (TextNode) p.childNode(0); + assertEquals("Hello", text.getWholeText()); + } + + @Test + public void testCommentEndBangCoverage() { + String html = " -->

Hello

"; + Document doc = Jsoup.parse(html); + + Element body = doc.body(); + Comment comment = (Comment) body.childNode(1); + assertEquals("
--!-", comment.getData()); + Element p = body.child(1); + TextNode text = (TextNode) p.childNode(0); + assertEquals("Hello", text.getWholeText()); + } + + @Test + public void testPublicIdentifiersWithWhitespace() { + String expectedOutput = ""; + for (char q : quote) { + for (char ws : whiteSpace) { + String[] htmls = { + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", q, q, ws), + String.format("", q, q, ws) + }; + for (String html : htmls) { + Document doc = Jsoup.parse(html); + assertEquals(expectedOutput, doc.childNode(0).outerHtml()); + } + } + } + } + + @Test + public void testSystemIdentifiersWithWhitespace() { + String expectedOutput = ""; + for (char q : quote) { + for (char ws : whiteSpace) { + String[] htmls = { + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", q, q, ws), + String.format("", q, q, ws) + }; + for (String html : htmls) { + Document doc = Jsoup.parse(html); + assertEquals(expectedOutput, doc.childNode(0).outerHtml()); + } + } + } + } + + @Test + public void testPublicAndSystemIdentifiersWithWhitespace() { + String expectedOutput = ""; + for (char q : quote) { + for (char ws : whiteSpace) { + String[] htmls = { + String.format("", q, q, ws, q, q), + String.format("", q, q, q, q) + }; + for (String html : htmls) { + Document doc = Jsoup.parse(html); + assertEquals(expectedOutput, doc.childNode(0).outerHtml()); + } + } + } + } +} From 3e4b76b6bbfc0ae1681d197dfacb3bf5afe0a5c1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 18:42:03 -0800 Subject: [PATCH 215/774] Deprecate validateTLSCertificates --- CHANGES | 2 ++ src/main/java/org/jsoup/Connection.java | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index efff23705f..c2ed12677b 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,8 @@ jsoup changelog This enables finding text that is only marked by a "br" tag, for example. + * Change: marked Connection.validateTLSCertificates() as deprecated. + * Improvement: normalize invisible characters (like soft-hyphens) in Element.text(). diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 76d9bcf0c5..29c4f19097 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -160,6 +160,8 @@ public final boolean hasBody() { *

* @param value if should validate TLS (SSL) certificates. true by default. * @return this Connection, for chaining + * @deprecated as distributions (specifically Google Play) are starting to show warnings if these checks are + * disabled. */ Connection validateTLSCertificates(boolean value); @@ -590,12 +592,15 @@ interface Request extends Base { /** * Get the current state of TLS (SSL) certificate validation. * @return true if TLS cert validation enabled + * @deprecated */ boolean validateTLSCertificates(); /** - * Set TLS certificate validation. + * Set TLS certificate validation. True by default. * @param value set false to ignore TLS (SSL) certificates + * @deprecated as distributions (specifically Google Play) are starting to show warnings if these checks are + * disabled. */ void validateTLSCertificates(boolean value); From 6b1f53eeafc49e6a96f868115c19509a13000d21 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 18:42:17 -0800 Subject: [PATCH 216/774] Spell check --- .../org/jsoup/parser/TokeniserStateTest.java | 18 +++++++++++++ .../org/jsoup/parser/TokenisetStateTest.java | 25 ------------------- 2 files changed, 18 insertions(+), 25 deletions(-) delete mode 100644 src/test/java/org/jsoup/parser/TokenisetStateTest.java diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index 926c7716e3..28bd552a4f 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -10,11 +10,29 @@ import org.jsoup.select.Elements; import org.junit.Test; +import java.util.Arrays; + public class TokeniserStateTest { final char[] whiteSpace = { '\t', '\n', '\r', '\f', ' ' }; final char[] quote = { '\'', '"' }; + @Test + public void ensureSearchArraysAreSorted() { + char[][] arrays = { + TokeniserState.attributeSingleValueCharsSorted, + TokeniserState.attributeDoubleValueCharsSorted, + TokeniserState.attributeNameCharsSorted, + TokeniserState.attributeValueUnquoted + }; + + for (char[] array : arrays) { + char[] copy = Arrays.copyOf(array, array.length); + Arrays.sort(array); + assertArrayEquals(array, copy); + } + } + @Test public void testCharacterReferenceInRcdata() { String body = ""; diff --git a/src/test/java/org/jsoup/parser/TokenisetStateTest.java b/src/test/java/org/jsoup/parser/TokenisetStateTest.java deleted file mode 100644 index e7ac1749a4..0000000000 --- a/src/test/java/org/jsoup/parser/TokenisetStateTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jsoup.parser; - -import org.junit.Test; - -import java.util.Arrays; - -import static org.junit.Assert.assertArrayEquals; - -public class TokenisetStateTest { - @Test - public void ensureSearchArraysAreSorted() { - char[][] arrays = { - TokeniserState.attributeSingleValueCharsSorted, - TokeniserState.attributeDoubleValueCharsSorted, - TokeniserState.attributeNameCharsSorted, - TokeniserState.attributeValueUnquoted - }; - - for (char[] array : arrays) { - char[] copy = Arrays.copyOf(array, array.length); - Arrays.sort(array); - assertArrayEquals(array, copy); - } - } -} From e6af571528c55832a015202ac4d8db9fc424d362 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 20 Nov 2017 11:43:10 +0900 Subject: [PATCH 217/774] Add Element#getWholeText() (#564) * Add Element#getWhoteText() * Add test case for nested nodes * Not trim() result string --- src/main/java/org/jsoup/nodes/Element.java | 21 +++++++++++++++++++ .../java/org/jsoup/nodes/ElementTest.java | 11 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index ba1470bf19..3f81da8714 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1042,6 +1042,27 @@ public void tail(Node node, int depth) { return accum.toString().trim(); } + /** + * Get the (unencoded) text of all children of this element, including any newlines and spaces present in the original. + * + * @return text + */ + public String getWholeText(){ + final StringBuilder accum = new StringBuilder(); + new NodeTraversor(new NodeVisitor() { + public void head(Node node, int depth) { + if (node instanceof TextNode) { + TextNode textNode = (TextNode) node; + accum.append(textNode.getWholeText()); + } + } + + public void tail(Node node, int depth) { + } + }).traverse(this); + return accum.toString(); + } + /** * Gets the text owned by this element only; does not get the combined text of all children. *

diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 5225bff182..24ea5610bb 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -120,6 +120,17 @@ public class ElementTest { assertEquals("Hello there", doc.text()); } + @Test public void testGetWholeText() { + Document doc = Jsoup.parse("

Hello\nthere

"); + assertEquals("Hello\nthere", doc.getWholeText()); + + doc = Jsoup.parse("

Hello \n there

"); + assertEquals("Hello \n there", doc.getWholeText()); + + doc = Jsoup.parse("

Hello

\n there

"); + assertEquals("Hello \n there", doc.getWholeText()); + } + @Test public void testGetSiblings() { Document doc = Jsoup.parse("

Hello

there

this

is

an

element

"); Element p = doc.getElementById("1"); From b001b304826d33f8f2b34fc7f14a931d9a97cf8a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 18:52:01 -0800 Subject: [PATCH 218/774] Renamed and changelog for wholeText --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Element.java | 15 +++++++++------ src/test/java/org/jsoup/nodes/ElementTest.java | 10 +++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index c2ed12677b..79764fad28 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,9 @@ jsoup changelog * Improvement: normalize invisible characters (like soft-hyphens) in Element.text(). + + * Improvement: added Element.wholeText(), to easily get the un-normalized text value of an element and its children. + * Bugfix: in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 3f81da8714..01a8cc880b 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1016,7 +1016,8 @@ public Elements getAllElements() { *

* For example, given HTML {@code

Hello there now!

}, {@code p.text()} returns {@code "Hello there now!"} * - * @return unencoded text, or empty string if none. + * @return unencoded, normalized text, or empty string if none. + * @see #wholeText() if you don't want the text to be normalized. * @see #ownText() * @see #textNodes() */ @@ -1043,13 +1044,15 @@ public void tail(Node node, int depth) { } /** - * Get the (unencoded) text of all children of this element, including any newlines and spaces present in the original. + * Get the (unencoded) text of all children of this element, including any newlines and spaces present in the + * original. * - * @return text + * @return unencoded, un-normalized text + * @see #text() */ - public String getWholeText(){ + public String wholeText() { final StringBuilder accum = new StringBuilder(); - new NodeTraversor(new NodeVisitor() { + NodeTraversor.traverse(new NodeVisitor() { public void head(Node node, int depth) { if (node instanceof TextNode) { TextNode textNode = (TextNode) node; @@ -1059,7 +1062,7 @@ public void head(Node node, int depth) { public void tail(Node node, int depth) { } - }).traverse(this); + }, this); return accum.toString(); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 24ea5610bb..7c73713a44 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -120,15 +120,15 @@ public class ElementTest { assertEquals("Hello there", doc.text()); } - @Test public void testGetWholeText() { - Document doc = Jsoup.parse("

Hello\nthere

"); - assertEquals("Hello\nthere", doc.getWholeText()); + @Test public void testWholeText() { + Document doc = Jsoup.parse("

Hello\nthere  

"); + assertEquals(" Hello\nthere   ", doc.wholeText()); doc = Jsoup.parse("

Hello \n there

"); - assertEquals("Hello \n there", doc.getWholeText()); + assertEquals("Hello \n there", doc.wholeText()); doc = Jsoup.parse("

Hello

\n there

"); - assertEquals("Hello \n there", doc.getWholeText()); + assertEquals("Hello \n there", doc.wholeText()); } @Test public void testGetSiblings() { From 7daae314a23160ee1f3e64e106c0d50d38d5ccdf Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 20 Nov 2017 03:53:23 +0100 Subject: [PATCH 219/774] Exclude examples from artifacts (#700) Added exclude rules in the pom.xml to prevent the classes from the org.jsoup.examples package to be included in the *.jar and *-sources.jar artifacts. --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 23ff5bed7c..c1134f7cfc 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,9 @@ maven-source-plugin 3.0.1 + + org/jsoup/examples/** + @@ -111,6 +114,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + org/jsoup/examples/** + From 6b5b09d21e5a712db88a76c4547e724de42475d5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 20:40:52 -0800 Subject: [PATCH 220/774] Relax test timeout a little --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 8e91a6a34f..ba07de986a 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -921,7 +921,7 @@ public class HtmlParserTest { // Assert assertEquals(2, doc.body().childNodeSize()); assertEquals(25000, doc.select("dd").size()); - assertTrue(System.currentTimeMillis() - start < 1000); + assertTrue(System.currentTimeMillis() - start < 2000); } @Test From 429f2c7db2b60683be76d4a82fb28a561f941909 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 20:42:15 -0800 Subject: [PATCH 221/774] [maven-release-plugin] prepare release jsoup-1.11.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c1134f7cfc..a1f6473bf5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.11.2-SNAPSHOT + 1.11.2 jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.11.2 Jonathan Hedley From bbef9d720df519581e457f3415bcccdde0dda382 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 20:42:24 -0800 Subject: [PATCH 222/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a1f6473bf5..b578655ea8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.11.2 + 1.11.3-SNAPSHOT jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.11.2 + HEAD Jonathan Hedley From 461609f686bf49177f125425c160c730700f0c7a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 19 Nov 2017 21:07:36 -0800 Subject: [PATCH 223/774] Release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 79764fad28..51d82cba92 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.11.2 [PENDING] +*** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. From c07c0eead5ec708303b7bfb54c28df2e7cad19e9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 23 Nov 2017 13:34:44 -0800 Subject: [PATCH 224/774] Preserve CDATA sections Fixes #406 Fixes #965 --- CHANGES | 6 +++ .../java/org/jsoup/helper/StringUtil.java | 2 +- src/main/java/org/jsoup/nodes/CDataNode.java | 44 +++++++++++++++++ src/main/java/org/jsoup/nodes/Element.java | 7 ++- src/main/java/org/jsoup/nodes/TextNode.java | 2 +- .../org/jsoup/parser/HtmlTreeBuilder.java | 13 +++-- src/main/java/org/jsoup/parser/Token.java | 21 +++++++- .../java/org/jsoup/parser/TokeniserState.java | 4 +- .../java/org/jsoup/parser/XmlTreeBuilder.java | 6 +-- .../java/org/jsoup/parser/HtmlParserTest.java | 49 ++++++++++++++++++- .../org/jsoup/parser/XmlTreeBuilderTest.java | 25 ++++++++++ 11 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/CDataNode.java diff --git a/CHANGES b/CHANGES index 51d82cba92..33e02426bb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,11 @@ jsoup changelog +*** Release 1.11.3 [PENDING] + * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are + round-tripped into output HTML. + + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index 9d2e7443a1..71aa7feb54 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -227,7 +227,7 @@ public static String resolve(final String baseUrl, final String relUrl) { } /** - * Maintains a cached StringBuilder, to minimize new StringBuilder GCs. Prevents it from growing to big per thread. + * Maintains a cached StringBuilder, to minimize new StringBuilder GCs. Prevents it from growing too big per thread. * Care must be taken to not grab more than one in the same stack (not locked or mutexed or anything). * @return an empty StringBuilder */ diff --git a/src/main/java/org/jsoup/nodes/CDataNode.java b/src/main/java/org/jsoup/nodes/CDataNode.java new file mode 100644 index 0000000000..369825619e --- /dev/null +++ b/src/main/java/org/jsoup/nodes/CDataNode.java @@ -0,0 +1,44 @@ +package org.jsoup.nodes; + +import org.jsoup.UncheckedIOException; + +import java.io.IOException; + +/** + * A Character Data node, to support CDATA sections. + */ +public class CDataNode extends TextNode { + public CDataNode(String text) { + super(text); + } + + @Override + public String nodeName() { + return "#cdata"; + } + + /** + * Get the unencoded, non-normalized text content of this CDataNode. + * @return unencoded, non-normalized text + */ + @Override + public String text() { + return getWholeText(); + } + + @Override + void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + accum + .append(""); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 01a8cc880b..208c366c34 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1097,7 +1097,7 @@ private void ownText(StringBuilder accum) { private static void appendNormalisedText(StringBuilder accum, TextNode textNode) { String text = textNode.getWholeText(); - if (preserveWhitespace(textNode.parentNode)) + if (preserveWhitespace(textNode.parentNode) || textNode instanceof CDataNode) accum.append(text); else StringUtil.appendNormalisedWhitespace(accum, text, TextNode.lastCharIsWhitespace(accum)); @@ -1180,6 +1180,11 @@ public String data() { Element element = (Element) childNode; String elementData = element.data(); sb.append(elementData); + } else if (childNode instanceof CDataNode) { + // this shouldn't really happen because the html parser won't see the cdata as anything special when parsing script. + // but incase another type gets through. + CDataNode cDataNode = (CDataNode) childNode; + sb.append(cDataNode.getWholeText()); } } return sb.toString(); diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 2037365ec4..dfd0e216b6 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -43,7 +43,7 @@ public String nodeName() { * @see TextNode#getWholeText() */ public String text() { - return normaliseWhitespace(getWholeText()); + return StringUtil.normaliseWhitespace(getWholeText()); } /** diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index b8cc1f03ad..93fe6aed7f 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -2,6 +2,7 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; import org.jsoup.nodes.DataNode; import org.jsoup.nodes.Document; @@ -255,11 +256,15 @@ void insert(Token.Comment commentToken) { void insert(Token.Character characterToken) { Node node; // characters in script and style go in as datanodes, not text nodes - String tagName = currentElement().tagName(); - if (tagName.equals("script") || tagName.equals("style")) - node = new DataNode(characterToken.getData()); + final String tagName = currentElement().tagName(); + final String data = characterToken.getData(); + + if (characterToken.isCData()) + node = new CDataNode(data); + else if (tagName.equals("script") || tagName.equals("style")) + node = new DataNode(data); else - node = new TextNode(characterToken.getData()); + node = new TextNode(data); currentElement().appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack. } diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 567d7b29d1..f21070e159 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -280,7 +280,7 @@ public String toString() { } } - final static class Character extends Token { + static class Character extends Token { private String data; Character() { @@ -309,6 +309,19 @@ public String toString() { } } + final static class CData extends Character { + CData(String data) { + super(); + this.data(data); + } + + @Override + public String toString() { + return ""; + } + + } + final static class EOF extends Token { EOF() { type = Token.TokenType.EOF; @@ -356,6 +369,10 @@ final boolean isCharacter() { return type == TokenType.Character; } + final boolean isCData() { + return this instanceof CData; + } + final Character asCharacter() { return (Character) this; } @@ -369,7 +386,7 @@ enum TokenType { StartTag, EndTag, Comment, - Character, + Character, // note no CData - treated in builder as an extension of Character EOF } } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 73e6de0e16..7d404259e3 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -917,6 +917,7 @@ void read(Tokeniser t, CharacterReader r) { // todo: should actually check current namepspace, and only non-html allows cdata. until namespace // is implemented properly, keep handling as cdata //} else if (!t.currentNodeInHtmlNS() && r.matchConsume("[CDATA[")) { + t.createTempBuffer(); t.transition(CdataSection); } else { t.error(this); @@ -1600,8 +1601,9 @@ void read(Tokeniser t, CharacterReader r) { CdataSection { void read(Tokeniser t, CharacterReader r) { String data = r.consumeTo("]]>"); - t.emit(data); + t.dataBuffer.append(data); if (r.matchConsume("]]>") || r.isEmpty()) { + t.emit(new Token.CData(t.dataBuffer.toString())); t.transition(Data); }// otherwise, buffer underrun, stay in data section } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index b136804118..65d304537f 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -96,9 +96,9 @@ void insert(Token.Comment commentToken) { insertNode(insert); } - void insert(Token.Character characterToken) { - Node node = new TextNode(characterToken.getData()); - insertNode(node); + void insert(Token.Character token) { + final String data = token.getData(); + insertNode(token.isCData() ? new CDataNode(data) : new TextNode(data)); } void insert(Token.Doctype d) { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index ba07de986a..c7a860bfed 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -4,7 +4,9 @@ import org.jsoup.TextUtil; import org.jsoup.helper.StringUtil; import org.jsoup.integration.ParseTest; +import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; +import org.jsoup.nodes.DataNode; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Entities; @@ -305,14 +307,57 @@ public class HtmlParserTest { @Test public void handlesCdata() { // todo: as this is html namespace, should actually treat as bogus comment, not cdata. keep as cdata for now - String h = "
\n<&]]>
"; // the & in there should remain literal + String h = "
\n <&]]>
"; // the & in there should remain literal Document doc = Jsoup.parse(h); Element div = doc.getElementById("1"); - assertEquals(" <&", div.text()); + assertEquals("\n <&", div.text()); assertEquals(0, div.children().size()); assertEquals(1, div.childNodeSize()); // no elements, one text node } + @Test public void roundTripsCdata() { + String h = "
\n <&]]>
"; + Document doc = Jsoup.parse(h); + Element div = doc.getElementById("1"); + assertEquals("\n <&", div.text()); + assertEquals(0, div.children().size()); + assertEquals(1, div.childNodeSize()); // no elements, one text node + + assertEquals("
\n <&]]>\n
", div.outerHtml()); + + CDataNode cdata = (CDataNode) div.textNodes().get(0); + assertEquals("\n\n <&", cdata.text()); + } + + @Test public void handlesCdataAcrossBuffer() { + StringBuilder sb = new StringBuilder(); + while (sb.length() <= CharacterReader.maxBufferLen) { + sb.append("A suitable amount of CData.\n"); + } + String cdata = sb.toString(); + String h = "
"; + Document doc = Jsoup.parse(h); + Element div = doc.selectFirst("div"); + + CDataNode node = (CDataNode) div.textNodes().get(0); + assertEquals(cdata, node.text()); + } + + @Test public void handlesCdataInScript() { + String html = ""; + Document doc = Jsoup.parse(html); + + String data = "//"; + Element script = doc.selectFirst("script"); + assertEquals("", script.text()); // won't be parsed as cdata because in script data section + assertEquals(data, script.data()); + assertEquals(html, script.outerHtml()); + + DataNode dataNode = (DataNode) script.childNode(0); + assertEquals(data, dataNode.getWholeData()); + // see - not a cdata node, because in script. contrast with XmlTreeBuilder - will be cdata. + } + @Test public void handlesUnclosedCdataAtEOF() { // https://github.com/jhy/jsoup/issues/349 would crash, as character reader would try to seek past EOF String h = "Check", TextUtil.stripNewlines(doc.html())); } + + @Test public void roundTripsCdata() { + String xml = "
\n <&]]>
"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + + Element div = doc.getElementById("1"); + assertEquals("\n <&", div.text()); + assertEquals(0, div.children().size()); + assertEquals(1, div.childNodeSize()); // no elements, one text node + + assertEquals("
\n <&]]>\n
", div.outerHtml()); + + CDataNode cdata = (CDataNode) div.textNodes().get(0); + assertEquals("\n\n <&", cdata.text()); + } + + @Test public void cdataPreservesWhiteSpace() { + String xml = ""; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + assertEquals(xml, doc.outerHtml()); + + assertEquals("//\n\n foo();\n//", doc.selectFirst("script").text()); + } } From 4c142cacf4d92b2214b3858e033d518ecdbf57f5 Mon Sep 17 00:00:00 2001 From: krystiangorecki Date: Fri, 24 Nov 2017 22:42:17 +0100 Subject: [PATCH 225/774] Deflate encoding support Fixes #556 Fixes #964 --- CHANGES | 4 ++++ src/main/java/org/jsoup/helper/HttpConnection.java | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 33e02426bb..86446de0fe 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog *** Release 1.11.3 [PENDING] + * Improvement: Deflate encoding is now supported. + + + * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 393eeaffcd..96a624b203 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -46,6 +46,8 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; import static org.jsoup.Connection.Method.HEAD; import static org.jsoup.internal.Normalizer.lowerCase; @@ -781,8 +783,11 @@ else if (methodHasBody) if (conn.getContentLength() != 0 && req.method() != HEAD) { // -1 means unknown, chunked. sun throws an IO exception on 500 response with no content when trying to read body res.bodyStream = null; res.bodyStream = conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream(); - if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) + if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) { res.bodyStream = new GZIPInputStream(res.bodyStream); + } else if (res.hasHeaderWithValue(CONTENT_ENCODING, "deflate")) { + res.bodyStream = new InflaterInputStream(res.bodyStream, new Inflater(true)); + } res.bodyStream = ConstrainableInputStream .wrap(res.bodyStream, DataUtil.bufferSize, req.maxBodySize()) .timeout(startTime, req.timeout()) From e9885f060d59dc2bd7351c5bd654a27cbe6de69a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 25 Nov 2017 10:33:01 -0800 Subject: [PATCH 226/774] More cdata coverage --- .../java/org/jsoup/parser/HtmlParserTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index c7a860bfed..8f22c3a085 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -365,6 +365,31 @@ public class HtmlParserTest { assertEquals(1, doc.body().childNodeSize()); } + @Test public void handleCDataInText() { + String h = "

One Three

"; + Document doc = Jsoup.parse(h); + Element p = doc.selectFirst("p"); + + List nodes = p.childNodes(); + assertEquals("One ", ((TextNode) nodes.get(0)).getWholeText()); + assertEquals("Two <&", ((TextNode) nodes.get(1)).getWholeText()); + assertEquals("Two <&", ((CDataNode) nodes.get(1)).getWholeText()); + assertEquals(" Three", ((TextNode) nodes.get(2)).getWholeText()); + + assertEquals(h, p.outerHtml()); + } + + @Test public void cdataNodesAreTextNodes() { + String h = "

One Three

"; + Document doc = Jsoup.parse(h); + Element p = doc.selectFirst("p"); + + List nodes = p.textNodes(); + assertEquals("One ", nodes.get(0).text()); + assertEquals(" Two <& ", nodes.get(1).text()); + assertEquals(" Three", nodes.get(2).text()); + } + @Test public void handlesInvalidStartTags() { String h = "
Hello < There <&>
"; // parse to
}> Document doc = Jsoup.parse(h); From 1791ef17ad6e0624a7b057b89439f7a33b3bbb82 Mon Sep 17 00:00:00 2001 From: Himanshu Garg Date: Thu, 30 Nov 2017 20:43:19 +0530 Subject: [PATCH 227/774] Adding support for nested quotes in attribute selection --- pom.xml | 7 +++ .../java/org/jsoup/parser/TokenQueue.java | 11 ++-- .../java/org/jsoup/parser/TokenQueueTest.java | 57 +++++++++++++------ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index b578655ea8..b95b71bb26 100644 --- a/pom.xml +++ b/pom.xml @@ -244,6 +244,13 @@ test + + pl.pragmatists + JUnitParams + 1.0.5 + test + + diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 5fa6f11eff..ffc8c14a22 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -262,15 +262,18 @@ public String chompBalanced(char open, char close) { int end = -1; int depth = 0; char last = 0; - boolean inQuote = false; + boolean inSingleQuote = false; + boolean inDoubleQuote = false; do { if (isEmpty()) break; Character c = consume(); if (last == 0 || last != ESC) { - if ((c.equals('\'') || c.equals('"')) && c != open) - inQuote = !inQuote; - if (inQuote) + if (c.equals('\'') && c != open && !inDoubleQuote) + inSingleQuote = !inSingleQuote; + if (c.equals('"') && c != open && !inSingleQuote) + inDoubleQuote = !inDoubleQuote; + if (inSingleQuote || inDoubleQuote) continue; if (c.equals(open)) { depth++; diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java index 030f3e8b61..5c7ae88e09 100644 --- a/src/test/java/org/jsoup/parser/TokenQueueTest.java +++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java @@ -1,11 +1,17 @@ package org.jsoup.parser; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.jsoup.Jsoup; import org.junit.Test; +import org.junit.runner.RunWith; + import static org.junit.Assert.*; /** * Token queue tests. */ +@RunWith(JUnitParamsRunner.class) public class TokenQueueTest { @Test public void chompBalanced() { TokenQueue tq = new TokenQueue(":contains(one (two) three) four"); @@ -17,7 +23,7 @@ public class TokenQueueTest { assertEquals("one (two) three", guts); assertEquals(" four", remainder); } - + @Test public void chompEscapedBalanced() { TokenQueue tq = new TokenQueue(":contains(one (two) \\( \\) \\) three) four"); String pre = tq.consumeTo("("); @@ -36,17 +42,17 @@ public class TokenQueueTest { String match = tq.chompBalanced('(', ')'); assertEquals("something(or another)", match); } - + @Test public void unescape() { assertEquals("one ( ) \\", TokenQueue.unescape("one \\( \\) \\\\")); } - + @Test public void chompToIgnoreCase() { String t = ""; TokenQueue tq = new TokenQueue(t); String data = tq.chompToIgnoreCase("one < two ", data); - + tq = new TokenQueue(" third "; - TokenQueue tq = new TokenQueue(t); - String data = tq.chompToIgnoreCase(""); - assertEquals(""); - assertEquals(" third ", data); + + + @Test public void consumeToIgnoreSecondCallTest() { + String t = " third "; + TokenQueue tq = new TokenQueue(t); + String data = tq.chompToIgnoreCase(""); + assertEquals(""); + assertEquals(" third ", data); + } + + @Test @Parameters(method = "singleQuotesInsideDouble, doubleQuotesInsideSingle") + public void testNestedQuotes(String html, String selector) { + assertEquals("#identifier", Jsoup.parse(html).select(selector).first().cssSelector()); + } + + public Object[] singleQuotesInsideDouble() { + return new Object[] {new String[] { + "", + "a[onclick*=\"('arg\"]"}, + new String[] {"", + "a[onclick*=\"('arg\"]"}}; + } + + public Object[] doubleQuotesInsideSingle() { + return new Object[] {new String[] { + "", + "a[onclick*='(\"arg']"}, + new String[] {"", + "a[onclick*='(\"arg']"}}; } - } From 64eef180f610d0dad97873f6786b103700ddb8b2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 3 Dec 2017 13:01:51 -0800 Subject: [PATCH 228/774] Test case for deflate --- CHANGES | 7 ++-- .../org/jsoup/integration/ConnectTest.java | 10 +++++ .../integration/servlets/Deflateservlet.java | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/servlets/Deflateservlet.java diff --git a/CHANGES b/CHANGES index 86446de0fe..716727b177 100644 --- a/CHANGES +++ b/CHANGES @@ -1,15 +1,14 @@ jsoup changelog *** Release 1.11.3 [PENDING] - * Improvement: Deflate encoding is now supported. - - - * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. + * Improvement: added support for Deflate encoding. + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 911dbe4fae..11b0e89955 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -2,6 +2,7 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; +import org.jsoup.integration.servlets.Deflateservlet; import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.HelloServlet; import org.jsoup.integration.servlets.SlowRider; @@ -366,4 +367,13 @@ public void multiCookieSet() throws IOException { Document doc = Jsoup.connect(echoUrl).cookies(cookies).get(); assertEquals("token=asdfg123; uid=jhy", ihVal("Cookie", doc)); } + + @Test + public void supportsDeflate() throws IOException { + Connection.Response res = Jsoup.connect(Deflateservlet.Url).execute(); + assertEquals("deflate", res.header("Content-Encoding")); + + Document doc = res.parse(); + assertEquals("Hello, World!", doc.selectFirst("p").text()); + } } diff --git a/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java b/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java new file mode 100644 index 0000000000..eaf4cfb071 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java @@ -0,0 +1,37 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.integration.TestServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +public class Deflateservlet extends BaseServlet { + public static final String Url = TestServer.map(Deflateservlet.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + res.setContentType(TextHtml); + res.setStatus(HttpServletResponse.SC_OK); + res.setHeader("Content-Encoding", "deflate"); + + String doc = "

Hello, World!

That should be enough, right?

Hello, World!

That should be enough, right?"; + + DeflaterOutputStream stream = new DeflaterOutputStream( + res.getOutputStream(), + new Deflater(Deflater.BEST_COMPRESSION, true)); // true = nowrap zlib headers + + stream.write(doc.getBytes(StandardCharsets.UTF_8)); + stream.close(); + } + + // allow the servlet to run as a main program, for local test + public static void main(String[] args) { + TestServer.start(); + System.out.println(Url); + } +} From f627193ce0950f0d55ed1b4f6a2fe9973447853a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 3 Dec 2017 14:38:17 -0800 Subject: [PATCH 229/774] Collapse boolean attribute empty string values Fixes #985 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Attribute.java | 9 ++++----- src/main/java/org/jsoup/nodes/Attributes.java | 4 +--- src/test/java/org/jsoup/nodes/ElementTest.java | 8 ++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 716727b177..3c1d88dc6f 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,9 @@ jsoup changelog * Improvement: added support for Deflate encoding. + * Fixed an issue where boolean attributes with empty string values were not collapsing in HTML output. + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 3163f9b9b0..698755c208 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -159,11 +159,10 @@ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { return shouldCollapseAttribute(key, val, out); } - protected static boolean shouldCollapseAttribute(String key, String val, Document.OutputSettings out) { - // todo: optimize - return (val == null || "".equals(val) || val.equalsIgnoreCase(key)) - && out.syntax() == Document.OutputSettings.Syntax.html - && isBooleanAttribute(key); + protected static boolean shouldCollapseAttribute(final String key, final String val, final Document.OutputSettings out) { + return ( + out.syntax() == Document.OutputSettings.Syntax.html && + (val == null || ("".equals(val) || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); } /** diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 188bbe37f0..1f74bf32b1 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -316,9 +316,7 @@ final void html(final Appendable accum, final Document.OutputSettings out) throw accum.append(' ').append(key); // collapse checked=null, checked="", checked=checked; write out others - if (!(out.syntax() == Document.OutputSettings.Syntax.html - && (val == null || val.equals(key) && Attribute.isBooleanAttribute(key)))) { - + if (!Attribute.shouldCollapseAttribute(key, val, out)) { accum.append("=\""); Entities.escape(accum, val == null ? EmptyString : val, out, true, false, false); accum.append('"'); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 7c73713a44..c5b4f0a3a2 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1310,4 +1310,12 @@ public void testRemovingEmptyClassAttributeWhenLastClassRemoved() { assertFalse(doc.body().html().contains("class=\"\"")); } + @Test + public void booleanAttributeOutput() { + Document doc = Jsoup.parse(""); + Element img = doc.selectFirst("img"); + + assertEquals("", img.outerHtml()); + } + } From f9307ec96a894191e5d3782601ddb49fbfc53ea6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 3 Dec 2017 15:13:40 -0800 Subject: [PATCH 230/774] Space for textnodes immediately after block elements --- CHANGES | 5 ++++- src/main/java/org/jsoup/nodes/Element.java | 7 +++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3c1d88dc6f..c97393c564 100644 --- a/CHANGES +++ b/CHANGES @@ -9,7 +9,10 @@ jsoup changelog * Improvement: added support for Deflate encoding. - * Fixed an issue where boolean attributes with empty string values were not collapsing in HTML output. + * Bugfix: The Element.text() for

One
Two was "OneTwo", not "One Two". + + + * Bugfix: boolean attributes with empty string values were not collapsing in HTML output. *** Release 1.11.2 [2017-Nov-19] diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 208c366c34..1f980e1c4e 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1038,6 +1038,13 @@ public void head(Node node, int depth) { } public void tail(Node node, int depth) { + // make sure there is a space between block tags and immediately following text nodes
One
Two should be "One Two". + if (node instanceof Element) { + Element element = (Element) node; + if (element.isBlock() && (node.nextSibling() instanceof TextNode) && !TextNode.lastCharIsWhitespace(accum)) + accum.append(' '); + } + } }, this); return accum.toString().trim(); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index c5b4f0a3a2..39f452b01f 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1318,4 +1318,10 @@ public void booleanAttributeOutput() { assertEquals("", img.outerHtml()); } + @Test + public void textHasSpaceAfterBlockTags() { + Document doc = Jsoup.parse("
One
Two"); + assertEquals("One Two", doc.text()); + } + } From 02668f757c59f0c1a7ad8f3169faf061b4b787c1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 3 Dec 2017 15:49:58 -0800 Subject: [PATCH 231/774] Skip first newline in

Fixes #825
---
 CHANGES                                                  | 3 +++
 src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 2 +-
 src/test/java/org/jsoup/parser/HtmlParserTest.java       | 7 +++++++
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/CHANGES b/CHANGES
index c97393c564..3392735df8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,9 @@ jsoup changelog
   * Improvement: added support for Deflate encoding.
     
 
+  * Improvement: when parsing 
 tags, skip the first newline if present.
+    
+
   * Bugfix: The Element.text() for 
One
Two was "OneTwo", not "One Two". diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index f86f8bcf38..a230677298 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -379,7 +379,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.processEndTag("p"); } tb.insert(startTag); - // todo: ignore LF if next token + tb.reader.matchConsume("\n"); // ignore LF if next token tb.framesetOk(false); } else if (name.equals("form")) { if (tb.getFormElement() != null) { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 8f22c3a085..95f4932c36 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1156,4 +1156,11 @@ public void testInvalidTableContents() throws IOException { Document doc = Jsoup.parse("\n\nOne\nTwo\n
"); + Element pre = doc.selectFirst("pre"); + assertEquals("One\nTwo", pre.text()); + assertEquals("\nOne\nTwo\n", pre.wholeText()); + } } From 45c54996c4e8d74cf84304e767934ff584c24702 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 21 Dec 2017 18:08:30 -0800 Subject: [PATCH 232/774] Make sure the mark limit is same as buffer size to prevent mark being invalidated Was bumping into two different buffer sizes when on Android <= 6, where the created buffer was larger than the underling OKio buffer that Android was using. Fixes #990 --- CHANGES | 3 +++ src/main/java/org/jsoup/helper/DataUtil.java | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3392735df8..6aee9a0139 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog * Improvement: when parsing
 tags, skip the first newline if present.
     
 
+  * Bugfix: "Mark has been invalidated" exception was thrown when parsing some URLs on Android <= 6.
+    
+
   * Bugfix: The Element.text() for 
One
Two was "OneTwo", not "One Two". diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 3ac0edb066..b9462dcc42 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -99,8 +99,8 @@ static Document parseInputStream(InputStream input, String charsetName, String b boolean fullyRead = false; // read the start of the stream and look for a BOM or meta charset - input.mark(firstReadBufferSize); - ByteBuffer firstBytes = readToByteBuffer(input, firstReadBufferSize - 1); // -1 because we read one more to see if completed + input.mark(bufferSize); + ByteBuffer firstBytes = readToByteBuffer(input, firstReadBufferSize - 1); // -1 because we read one more to see if completed. First read is < buffer size, so can't be invalid. fullyRead = input.read() == -1; input.reset(); From bf4f99c72ba3d59486e0decb59a2b87edee4f1ff Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 21 Dec 2017 18:36:16 -0800 Subject: [PATCH 233/774] Prep #988 for merge --- CHANGES | 3 ++ pom.xml | 7 ----- .../java/org/jsoup/parser/TokenQueue.java | 3 +- .../java/org/jsoup/parser/TokenQueueTest.java | 30 +++++-------------- 4 files changed, 13 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index 6aee9a0139..97bd61d57e 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog * Improvement: when parsing
 tags, skip the first newline if present.
     
 
+  * Improvement: support nested quotes for attribute selection queries.
+    
+
   * Bugfix: "Mark has been invalidated" exception was thrown when parsing some URLs on Android <= 6.
     
 
diff --git a/pom.xml b/pom.xml
index b95b71bb26..b578655ea8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -244,13 +244,6 @@
       test
     
 
-    
-      pl.pragmatists
-      JUnitParams
-      1.0.5
-      test
-    
-
   
 
   
diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java
index ffc8c14a22..df7d925b8c 100644
--- a/src/main/java/org/jsoup/parser/TokenQueue.java
+++ b/src/main/java/org/jsoup/parser/TokenQueue.java
@@ -271,10 +271,11 @@ public String chompBalanced(char open, char close) {
             if (last == 0 || last != ESC) {
                 if (c.equals('\'') && c != open && !inDoubleQuote)
                     inSingleQuote = !inSingleQuote;
-                if (c.equals('"') && c != open && !inSingleQuote)
+                else if (c.equals('"') && c != open && !inSingleQuote)
                     inDoubleQuote = !inDoubleQuote;
                 if (inSingleQuote || inDoubleQuote)
                     continue;
+
                 if (c.equals(open)) {
                     depth++;
                     if (start == -1)
diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java
index 5c7ae88e09..f169552c62 100644
--- a/src/test/java/org/jsoup/parser/TokenQueueTest.java
+++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java
@@ -1,17 +1,13 @@
 package org.jsoup.parser;
 
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
 import org.jsoup.Jsoup;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 /**
  * Token queue tests.
  */
-@RunWith(JUnitParamsRunner.class)
 public class TokenQueueTest {
     @Test public void chompBalanced() {
         TokenQueue tq = new TokenQueue(":contains(one (two) three) four");
@@ -76,24 +72,14 @@ public class TokenQueueTest {
         assertEquals(" third ", data);
     }
 
-    @Test @Parameters(method = "singleQuotesInsideDouble, doubleQuotesInsideSingle")
-    public void testNestedQuotes(String html, String selector) {
-        assertEquals("#identifier", Jsoup.parse(html).select(selector).first().cssSelector());
-    }
-
-    public Object[] singleQuotesInsideDouble() {
-        return new Object[] {new String[] {
-            "",
-            "a[onclick*=\"('arg\"]"},
-            new String[] {"",
-                "a[onclick*=\"('arg\"]"}};
+    @Test public void testNestedQuotes() {
+        validateNestedQuotes("", "a[onclick*=\"('arg\"]");
+        validateNestedQuotes("", "a[onclick*=\"('arg\"]");
+        validateNestedQuotes("", "a[onclick*='(\"arg']");
+        validateNestedQuotes("", "a[onclick*='(\"arg']");
     }
 
-    public Object[] doubleQuotesInsideSingle() {
-        return new Object[] {new String[] {
-            "",
-            "a[onclick*='(\"arg']"},
-            new String[] {"",
-                "a[onclick*='(\"arg']"}};
+    private static void validateNestedQuotes(String html, String selector) {
+        assertEquals("#identifier", Jsoup.parse(html).select(selector).first().cssSelector());
     }
 }

From df272b77c2cf89e9cbe2512bbddf8a3bc28a704b Mon Sep 17 00:00:00 2001
From: Jonathan Hedley 
Date: Fri, 22 Dec 2017 10:25:05 -0800
Subject: [PATCH 234/774] In XML parse mode set to lowercase, normalize closing
 tags

Fixes #998
---
 CHANGES                                                | 4 ++++
 src/main/java/org/jsoup/parser/XmlTreeBuilder.java     | 2 +-
 src/test/java/org/jsoup/parser/HtmlParserTest.java     | 5 +++++
 src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java | 7 +++++++
 4 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/CHANGES b/CHANGES
index 97bd61d57e..8bf1d00226 100644
--- a/CHANGES
+++ b/CHANGES
@@ -24,6 +24,10 @@ jsoup changelog
   * Bugfix: boolean attributes with empty string values were not collapsing in HTML output.
     
 
+  * Bugfix: when using the XML Parser set to lowercase normalize tags, uppercase closing tags were not correctly
+    handled.
+    
+
 *** Release 1.11.2 [2017-Nov-19]
   * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements.
     This enables finding text that is only marked by a "br" tag, for example.
diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java
index 65d304537f..08518333a5 100644
--- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java
+++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java
@@ -114,7 +114,7 @@ void insert(Token.Doctype d) {
      * @param endTag tag to close
      */
     private void popStackToClose(Token.EndTag endTag) {
-        String elName = endTag.name();
+        String elName = endTag.normalName();
         Element firstFound = null;
 
         for (int pos = stack.size() -1; pos >= 0; pos--) {
diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java
index 95f4932c36..0a76bb9eb0 100644
--- a/src/test/java/org/jsoup/parser/HtmlParserTest.java
+++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java
@@ -1105,6 +1105,11 @@ public void testInvalidTableContents() throws IOException {
         assertEquals("  A   B  ", StringUtil.normaliseWhitespace(doc.body().html()));
     }
 
+    @Test public void normalizesDiscordantTags() {
+        Document document = Jsoup.parse("
test

"); + assertEquals("
\n test\n
\n

", document.body().html()); + } + @Test public void selfClosingVoidIsNotAnError() { String html = "

test
test

"; Parser parser = Parser.htmlParser().setTrackErrors(5); diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 40be145848..cb5bb14631 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -191,6 +191,13 @@ public void canNormalizeCase() { assertEquals("Check", TextUtil.stripNewlines(doc.html())); } + @Test public void normalizesDiscordantTags() { + Parser parser = Parser.xmlParser().settings(ParseSettings.htmlDefault); + Document document = Jsoup.parse("
test

", "", parser); + assertEquals("
\n test\n
\n

", document.html()); + // was failing -> toString() = "
\n test\n

\n
" + } + @Test public void roundTripsCdata() { String xml = "
\n <&]]>
"; Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); From 11e018fe623758986910069a6285b614dd98cc98 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 22 Dec 2017 12:44:14 -0800 Subject: [PATCH 235/774] Verify that form data mixes ok with form uploads To test #983 --- src/test/java/org/jsoup/integration/ConnectTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 11b0e89955..cdc253636d 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -285,11 +285,13 @@ public void postFiles() throws IOException { Document res = Jsoup .connect(EchoServlet.Url) + .data("firstname", "Jay") .data("firstPart", thumb.getName(), new FileInputStream(thumb), "image/jpeg") .data("secondPart", html.getName(), new FileInputStream(html)) // defaults to "application-octetstream"; + .data("surname", "Soup") .post(); - assertEquals("2", ihVal("Parts", res)); + assertEquals("4", ihVal("Parts", res)); assertEquals("application/octet-stream", ihVal("Part secondPart ContentType", res)); assertEquals("secondPart", ihVal("Part secondPart Name", res)); @@ -306,6 +308,9 @@ public void postFiles() throws IOException { assertEquals("thumb.jpg", ihVal("Part firstPart Filename", res)); assertEquals("1052", ihVal("Part firstPart Size", res)); + assertEquals("Jay", ihVal("firstname", res)); + assertEquals("Soup", ihVal("surname", res)); + /* Part secondPart ContentTypeapplication/octet-stream Part secondPart NamesecondPart From e5210d1f9ef4f1d41ff0a8c4a2ab8e9192d5e087 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 22 Dec 2017 13:49:47 -0800 Subject: [PATCH 236/774] Corrected fix for #998 --- src/main/java/org/jsoup/parser/XmlTreeBuilder.java | 2 +- src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 08518333a5..10bb54a326 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -114,7 +114,7 @@ void insert(Token.Doctype d) { * @param endTag tag to close */ private void popStackToClose(Token.EndTag endTag) { - String elName = endTag.normalName(); + String elName = settings.normalizeTag(endTag.tagName); Element firstFound = null; for (int pos = stack.size() -1; pos >= 0; pos--) { diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index cb5bb14631..e5d6f01cb3 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -179,9 +179,9 @@ public void testCreatesValidProlog() { @Test public void preservesCaseByDefault() { - String xml = "Check"; + String xml = "OneCheck"; Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); - assertEquals("Check", TextUtil.stripNewlines(doc.html())); + assertEquals("OneCheck", TextUtil.stripNewlines(doc.html())); } @Test From b92b4f6b9b3256e97bfb6a0732bf113b6da53a4c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 23 Dec 2017 17:40:13 -0800 Subject: [PATCH 237/774] isEmpty() should bufferUp isEmpty wasn't checking to see if more content was able to be read, so when called directly without a consume() could give misleading results. Fixes #995 --- CHANGES | 3 +++ .../org/jsoup/parser/CharacterReader.java | 24 ++++++++++++------- .../org/jsoup/parser/CharacterReaderTest.java | 16 +++++++++++++ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 8bf1d00226..8ad43238dd 100644 --- a/CHANGES +++ b/CHANGES @@ -69,6 +69,9 @@ jsoup changelog * Bugfix: in the W3CDom transformer, siblings were incorrectly inheriting namespaces defined on previous siblings. + * Bugfix: when parsing from a URL, an end tag could be read incorrectly if it started on a buffer boundary. + + *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 8269e7949d..b3b82e9cf4 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -48,14 +48,17 @@ private void bufferUp() { return; try { - readerPos += bufPos; reader.skip(bufPos); reader.mark(maxBufferLen); - bufLength = reader.read(charBuf); + final int read = reader.read(charBuf); reader.reset(); - bufPos = 0; - bufMark = 0; - bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; + if (read != -1) { + bufLength = read; + readerPos += bufPos; + bufPos = 0; + bufMark = 0; + bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; + } } catch (IOException e) { throw new UncheckedIOException(e); } @@ -74,6 +77,11 @@ public int pos() { * @return true if nothing left to read. */ public boolean isEmpty() { + bufferUp(); + return bufPos >= bufLength; + } + + private boolean isEmptyNoBufferUp() { return bufPos >= bufLength; } @@ -83,12 +91,12 @@ public boolean isEmpty() { */ public char current() { bufferUp(); - return isEmpty() ? EOF : charBuf[bufPos]; + return isEmptyNoBufferUp() ? EOF : charBuf[bufPos]; } char consume() { bufferUp(); - char val = isEmpty() ? EOF : charBuf[bufPos]; + char val = isEmptyNoBufferUp() ? EOF : charBuf[bufPos]; bufPos++; return val; } @@ -281,7 +289,7 @@ String consumeLetterThenDigitSequence() { else break; } - while (!isEmpty()) { + while (!isEmptyNoBufferUp()) { char c = charBuf[bufPos]; if (c >= '0' && c <= '9') bufPos++; diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 1325eb262d..b058db0c54 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -1,7 +1,10 @@ package org.jsoup.parser; +import org.junit.Ignore; import org.junit.Test; +import java.io.StringReader; + import static org.junit.Assert.*; /** @@ -268,5 +271,18 @@ public void consumeToNonexistentEndWhenAtAnd() { assertTrue(r.isEmpty()); } + @Ignore + @Test + public void notEmptyAtBufferSplitPoint() { + CharacterReader r = new CharacterReader(new StringReader("How about now"), 3); + assertEquals("How", r.consumeTo(' ')); + assertFalse("Should not be empty", r.isEmpty()); + + assertEquals(' ', r.consume()); + assertFalse(r.isEmpty()); + + // todo - current consume to won't expand buffer. impl buffer extension and test + } + } From 03bffef1c7f6652f28214961bac16ad837064d32 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 23 Dec 2017 18:08:56 -0800 Subject: [PATCH 238/774] Correct the change note location --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 8ad43238dd..d4bc3c2102 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,9 @@ jsoup changelog handled. + * Bugfix: when parsing from a URL, an end tag could be read incorrectly if it started on a buffer boundary. + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. @@ -69,9 +72,6 @@ jsoup changelog * Bugfix: in the W3CDom transformer, siblings were incorrectly inheriting namespaces defined on previous siblings. - * Bugfix: when parsing from a URL, an end tag could be read incorrectly if it started on a buffer boundary. - - *** Release 1.11.1 [2017-Nov-06] * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are not used. From f1110a9021c2caa28cbe3177c0c3a0f5ae326eb4 Mon Sep 17 00:00:00 2001 From: Olek Janiszewski Date: Sat, 7 Apr 2018 10:39:25 +0200 Subject: [PATCH 239/774] Accept a custom SSL socket factory --- CHANGES | 2 ++ src/main/java/org/jsoup/Connection.java | 21 +++++++++++++++++++ .../java/org/jsoup/helper/HttpConnection.java | 20 +++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d4bc3c2102..8024cd17cd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ jsoup changelog *** Release 1.11.3 [PENDING] + * Improvement: accept a custom SSL socket factory + * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 29c4f19097..97bd969bd0 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Map; +import javax.net.ssl.SSLSocketFactory; + /** * A Connection provides a convenient interface to fetch content from the web, and parse them into Documents. *

@@ -165,6 +167,13 @@ public final boolean hasBody() { */ Connection validateTLSCertificates(boolean value); + /** + * Set custom SSL socket factory + * @param sslSocketFactory custom SSL socket factory + * @return this Connection, for chaining + */ + Connection sslSocketFactory(SSLSocketFactory sslSocketFactory); + /** * Add a request data parameter. Request parameters are sent in the request query string for GETs, and in the * request body for POSTs. A request may have multiple values of the same name. @@ -604,6 +613,18 @@ interface Request extends Base { */ void validateTLSCertificates(boolean value); + /** + * Get the custom SSL socket factory + * @return custom SSL socket factory + */ + SSLSocketFactory sslSocketFactory(); + + /** + * Set custom SSL socket factory. + * @param sslSocketFactory SSL socket factory + */ + void sslSocketFactory(SSLSocketFactory sslSocketFactory); + /** * Add a data parameter to the request * @param keyval data to add. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 96a624b203..b7aa5f151f 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -200,6 +200,11 @@ public Connection data(String key, String value) { return this; } + public Connection sslSocketFactory(SSLSocketFactory sslSocketFactory) { + req.sslSocketFactory(sslSocketFactory); + return this; + } + public Connection data(String key, String filename, InputStream inputStream) { req.data(KeyVal.create(key, filename, inputStream)); return this; @@ -549,6 +554,7 @@ public static class Request extends HttpConnection.Base impl private boolean parserDefined = false; // called parser(...) vs initialized in ctor private boolean validateTSLCertificates = true; private String postDataCharset = DataUtil.defaultCharset; + private SSLSocketFactory sslSocketFactory; Request() { timeoutMilliseconds = 30000; // 30 seconds @@ -616,6 +622,14 @@ public void validateTLSCertificates(boolean value) { validateTSLCertificates = value; } + public SSLSocketFactory sslSocketFactory() { + return sslSocketFactory; + } + + public void sslSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + public Connection.Request ignoreHttpErrors(boolean ignoreHttpErrors) { this.ignoreHttpErrors = ignoreHttpErrors; return this; @@ -901,7 +915,11 @@ private static HttpURLConnection createConnection(Connection.Request req) throws conn.setReadTimeout(req.timeout() / 2); // gets reduced after connection is made and status is read if (conn instanceof HttpsURLConnection) { - if (!req.validateTLSCertificates()) { + SSLSocketFactory socketFactory = req.sslSocketFactory(); + + if (socketFactory != null) { + ((HttpsURLConnection) conn).setSSLSocketFactory(socketFactory); + } else if (!req.validateTLSCertificates()) { initUnSecureTSL(); ((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory); ((HttpsURLConnection)conn).setHostnameVerifier(getInsecureVerifier()); From 71561e09e0f29ec5fd1bb918206f3d8e42876518 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 7 Apr 2018 19:38:58 -0700 Subject: [PATCH 240/774] It's 2018 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index fe9cc78c98..1dfb4cb3b8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2009-2017 Jonathan Hedley +Copyright (c) 2009-2018 Jonathan Hedley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 2c4e79b104c0ff32566ce247617d47c0b39cc2c7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Apr 2018 13:27:36 -0700 Subject: [PATCH 241/774] DataUtil should throw IOException on failed reads during parse Fixes #980 --- CHANGES | 5 +++ src/main/java/org/jsoup/Connection.java | 1 + .../java/org/jsoup/UncheckedIOException.java | 2 +- src/main/java/org/jsoup/helper/DataUtil.java | 8 ++++- .../org/jsoup/integration/ConnectTest.java | 36 +++++++++++++++++++ .../servlets/InterruptedServlet.java | 31 ++++++++++++++++ .../org/jsoup/parser/CharacterReaderTest.java | 1 + 7 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java diff --git a/CHANGES b/CHANGES index d4bc3c2102..73eb2cd8be 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,11 @@ jsoup changelog * Bugfix: when parsing from a URL, an end tag could be read incorrectly if it started on a buffer boundary. + * Bugfix: when parsing from a URL, if the remote server failed to complete its write (i.e. it writes less than the + Content Length header promised on a gzipped stream), the parse method would incorrectly throw an unchecked + exception. It now throws the declared IOException. + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 29c4f19097..57dc878a65 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -724,6 +724,7 @@ interface Response extends Base { * same connection response (otherwise, once the response is read, its InputStream will have been drained and * may not be re-read). Calling {@link #body() } or {@link #bodyAsBytes()} has the same effect. * @return this response, for chaining + * @throws UncheckedIOException if an IO exception occurs during buffering. */ Response bufferUp(); diff --git a/src/main/java/org/jsoup/UncheckedIOException.java b/src/main/java/org/jsoup/UncheckedIOException.java index e9a91df903..3d05babbb3 100644 --- a/src/main/java/org/jsoup/UncheckedIOException.java +++ b/src/main/java/org/jsoup/UncheckedIOException.java @@ -2,7 +2,7 @@ import java.io.IOException; -public class UncheckedIOException extends Error { +public class UncheckedIOException extends RuntimeException { public UncheckedIOException(IOException cause) { super(cause); } diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index b9462dcc42..3037d619cd 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -1,5 +1,6 @@ package org.jsoup.helper; +import org.jsoup.UncheckedIOException; import org.jsoup.internal.ConstrainableInputStream; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -148,7 +149,12 @@ static Document parseInputStream(InputStream input, String charsetName, String b if (charsetName == null) charsetName = defaultCharset; BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); - doc = parser.parseInput(reader, baseUri); + try { + doc = parser.parseInput(reader, baseUri); + } catch (UncheckedIOException e) { + // io exception when parsing (not seen before because reading the stream as we go) + throw e.ioException(); + } doc.outputSettings().charset(charsetName); } input.close(); diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index cdc253636d..b6e1a1863a 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -2,9 +2,11 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; +import org.jsoup.UncheckedIOException; import org.jsoup.integration.servlets.Deflateservlet; import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.HelloServlet; +import org.jsoup.integration.servlets.InterruptedServlet; import org.jsoup.integration.servlets.SlowRider; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -381,4 +383,38 @@ public void supportsDeflate() throws IOException { Document doc = res.parse(); assertEquals("Hello, World!", doc.selectFirst("p").text()); } + + @Test + public void handlesEmptyStreamDuringParseRead() throws IOException { + // this handles situations where the remote server sets a content length greater than it actually writes + + Connection.Response res = Jsoup.connect(InterruptedServlet.Url) + .timeout(200) + .execute(); + + boolean threw = false; + try { + Document document = res.parse(); + assertEquals("Something", document.title()); + } catch (IOException e) { + threw = true; + } + assertEquals(true, threw); + } + + @Test + public void handlesEmtpyStreamDuringBufferdRead() throws IOException { + Connection.Response res = Jsoup.connect(InterruptedServlet.Url) + .timeout(200) + .execute(); + + boolean threw = false; + try { + res.bufferUp(); + } catch (UncheckedIOException e) { + threw = true; + } + assertEquals(true, threw); + + } } diff --git a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java new file mode 100644 index 0000000000..f403be9f00 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java @@ -0,0 +1,31 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.integration.TestServer; +import org.jsoup.parser.CharacterReaderTest; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class InterruptedServlet extends BaseServlet { + public static final String Url = TestServer.map(InterruptedServlet.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + res.setContentType(TextHtml); + res.setStatus(HttpServletResponse.SC_OK); + + StringBuilder sb = new StringBuilder(); + sb.append("Something"); + while (sb.length() <= CharacterReaderTest.maxBufferLen) { + sb.append("A suitable amount of data. \n"); + } + String data = sb.toString(); + + res.setContentLength(data.length() * 2); + + res.getWriter().write(data); + + } +} diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index b058db0c54..ad9e32b02f 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -13,6 +13,7 @@ * @author Jonathan Hedley, jonathan@hedley.net */ public class CharacterReaderTest { + public final static int maxBufferLen = CharacterReader.maxBufferLen; @Test public void consume() { CharacterReader r = new CharacterReader("one"); From 1028b37a1dfbc5dda7b18cbe692ab168c54fb505 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Apr 2018 13:43:05 -0700 Subject: [PATCH 242/774] Leaf nodes should return an empty list on .childNodes(), not except Fixes #1032 --- CHANGES | 8 ++++++-- src/main/java/org/jsoup/nodes/LeafNode.java | 5 ++++- src/test/java/org/jsoup/nodes/TextNodeTest.java | 10 ++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 73eb2cd8be..c3918ca412 100644 --- a/CHANGES +++ b/CHANGES @@ -29,12 +29,16 @@ jsoup changelog * Bugfix: when parsing from a URL, an end tag could be read incorrectly if it started on a buffer boundary. - + * Bugfix: when parsing from a URL, if the remote server failed to complete its write (i.e. it writes less than the Content Length header promised on a gzipped stream), the parse method would incorrectly throw an unchecked exception. It now throws the declared IOException. - + + + * Bugfix: leaf nodes (such as text nodes) where throwing an unsupported operation exception on childNodes(), instead + of just returning an empty list. + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. diff --git a/src/main/java/org/jsoup/nodes/LeafNode.java b/src/main/java/org/jsoup/nodes/LeafNode.java index 77bac9b8ef..ea3605ddc3 100644 --- a/src/main/java/org/jsoup/nodes/LeafNode.java +++ b/src/main/java/org/jsoup/nodes/LeafNode.java @@ -2,9 +2,12 @@ import org.jsoup.helper.Validate; +import java.util.Collections; import java.util.List; abstract class LeafNode extends Node { + private static final List EmptyNodes = Collections.emptyList(); + Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set) protected final boolean hasAttributes() { @@ -90,6 +93,6 @@ public int childNodeSize() { @Override protected List ensureChildNodes() { - throw new UnsupportedOperationException("Leaf Nodes do not have child nodes."); + return EmptyNodes; } } diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index 42b35ad559..1857c0567b 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -4,6 +4,8 @@ import org.jsoup.TextUtil; import org.junit.Test; +import java.util.List; + import static org.junit.Assert.*; /** @@ -72,4 +74,12 @@ public class TextNodeTest { TextNode t = doc.body().textNodes().get(0); assertEquals(new String(Character.toChars(135361)), t.outerHtml().trim()); } + + @Test public void testLeadNodesHaveNoChildren() { + Document doc = Jsoup.parse("

Hello there
"); + Element div = doc.select("div").first(); + TextNode tn = (TextNode) div.childNode(0); + List nodes = tn.childNodes(); + assertEquals(0, nodes.size()); + } } From 197815a029b9a58566655b0ef343bb8c6a57fb2e Mon Sep 17 00:00:00 2001 From: Julio <7751270+GalanJ@users.noreply.github.com> Date: Sat, 14 Apr 2018 13:48:08 -0700 Subject: [PATCH 243/774] Fix typo in README.md (#1028) Fixed a small typo I found when reading through this file. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3432bed8ea..f39167e104 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ for (Element headline : newsHeadlines) { jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available at [GitHub](https://github.com/jhy/jsoup/tree/master/src/main/java/org/jsoup). ## Getting started -1. [Download](https://jsoup.org/download) the latest jsoup jar (or it add to your Maven/Gradle build) +1. [Download](https://jsoup.org/download) the latest jsoup jar (or add it to your Maven/Gradle build) 2. Read the [cookbook](https://jsoup.org/cookbook/) 3. Enjoy! From f37e6481d3acf115bfe09a699b6b20b93522f944 Mon Sep 17 00:00:00 2001 From: Colorize Date: Sat, 14 Apr 2018 22:54:21 +0200 Subject: [PATCH 244/774] Allow HtmlTreeBuilder subclasses (#1040) * Remove package-private constructor to allow subclasses for HtmlTreeBuilder * Make TokenType visible from outside the org.jsoup package --- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 -- src/main/java/org/jsoup/parser/Token.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 93fe6aed7f..efc86bdac1 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -56,8 +56,6 @@ public class HtmlTreeBuilder extends TreeBuilder { private boolean fosterInserts; // if next inserts should be fostered private boolean fragmentParsing; // if parsing a fragment of html - HtmlTreeBuilder() {} - ParseSettings defaultSettings() { return ParseSettings.htmlDefault; } diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index f21070e159..e1fde43325 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -381,7 +381,7 @@ final boolean isEOF() { return type == TokenType.EOF; } - enum TokenType { + public enum TokenType { Doctype, StartTag, EndTag, From a4dfbcf090102e13949cc15a973a9ad2721d98d0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Apr 2018 17:18:06 -0700 Subject: [PATCH 245/774] Set automatic module name for Java 9 Fixes 1019. --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index b578655ea8..4061cf22e6 100644 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,9 @@ 3.0.2 + + org.jsoup + ${project.build.outputDirectory}/META-INF/MANIFEST.MF From 0f7e0cc373aced32629f5321c9521f81d8248647 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 14 Apr 2018 20:08:25 -0700 Subject: [PATCH 246/774] Make sure to consume a UTF-8 BOM at the start of a doc Fixes #1003 --- CHANGES | 4 ++++ src/main/java/org/jsoup/helper/DataUtil.java | 16 ++++++++-------- .../org/jsoup/parser/HtmlTreeBuilderState.java | 8 +------- src/test/java/org/jsoup/helper/DataUtilTest.java | 7 +++++++ .../java/org/jsoup/parser/HtmlParserTest.java | 10 ++++++++++ src/test/resources/bomtests/bom_utf8.html | 10 ++++++++++ src/test/resources/htmltests/comments.html | 16 ++++++++++++++++ 7 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/bomtests/bom_utf8.html create mode 100644 src/test/resources/htmltests/comments.html diff --git a/CHANGES b/CHANGES index c3918ca412..eb596b3176 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,10 @@ jsoup changelog of just returning an empty list. + * Bugfix: documents with a leading UTF-8 BOM did not have that BOM consumed, so it acted as a zero width no-break + space, which could impact the parse tree. + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 3037d619cd..5f276eec91 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -107,10 +107,8 @@ static Document parseInputStream(InputStream input, String charsetName, String b // look for BOM - overrides any other header or input BomCharset bomCharset = detectCharsetFromBom(firstBytes); - if (bomCharset != null) { + if (bomCharset != null) charsetName = bomCharset.charset; - input.skip(bomCharset.offset); - } if (charsetName == null) { // determine from meta. safe first parse as UTF-8 String docData = Charset.forName(defaultCharset).decode(firstBytes).toString(); @@ -149,6 +147,8 @@ static Document parseInputStream(InputStream input, String charsetName, String b if (charsetName == null) charsetName = defaultCharset; BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); + if (bomCharset != null && bomCharset.offset) // creating the buffered reader ignores the input pos, so must skip here + reader.skip(1); try { doc = parser.parseInput(reader, baseUri); } catch (UncheckedIOException e) { @@ -248,12 +248,12 @@ private static BomCharset detectCharsetFromBom(final ByteBuffer byteData) { } if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF || // BE bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { // LE - return new BomCharset("UTF-32", 0); // and I hope it's on your system + return new BomCharset("UTF-32", false); // and I hope it's on your system } else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF || // BE bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) { - return new BomCharset("UTF-16", 0); // in all Javas + return new BomCharset("UTF-16", false); // in all Javas } else if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) { - return new BomCharset("UTF-8", 3); // in all Javas + return new BomCharset("UTF-8", true); // in all Javas // 16 and 32 decoders consume the BOM to determine be/le; utf-8 should be consumed here } return null; @@ -261,9 +261,9 @@ private static BomCharset detectCharsetFromBom(final ByteBuffer byteData) { private static class BomCharset { private final String charset; - private final int offset; + private final boolean offset; - public BomCharset(String charset, int offset) { + public BomCharset(String charset, boolean offset) { this.charset = charset; this.offset = offset; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index a230677298..26d7d7ac14 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1479,13 +1479,7 @@ private static boolean isWhitespace(Token t) { } private static boolean isWhitespace(String data) { - // todo: this checks more than spec - "\t", "\n", "\f", "\r", " " - for (int i = 0; i < data.length(); i++) { - char c = data.charAt(i); - if (!StringUtil.isWhitespace(c)) - return false; - } - return true; + return StringUtil.isBlank(data); } private static void handleRcData(Token.StartTag startTag, HtmlTreeBuilder tb) { diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 70185d3cfe..7021676d4e 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -157,4 +157,11 @@ public void supportsBOMinFiles() throws IOException { assertTrue(doc.title().contains("UTF-32LE")); assertTrue(doc.text().contains("가각갂갃간갅")); } + + @Test + public void supportsUTF8BOM() throws IOException { + File in = getFile("/bomtests/bom_utf8.html"); + Document doc = Jsoup.parse(in, null, "http://example.com"); + assertEquals("OK", doc.head().select("title").text()); + } } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 0a76bb9eb0..4032e7c1b9 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1168,4 +1168,14 @@ public void testInvalidTableContents() throws IOException { assertEquals("One\nTwo", pre.text()); assertEquals("\nOne\nTwo\n", pre.wholeText()); } + + @Test public void handlesXmlDeclAndCommentsBeforeDoctype() throws IOException { + File in = ParseTest.getFile("/htmltests/comments.html"); + Document doc = Jsoup.parse(in, "UTF-8"); + + assertEquals(" A Certain Kind of Test

Hello

h1> (There is a UTF8 hidden BOM at the top of this file.) ", + StringUtil.normaliseWhitespace(doc.html())); + + assertEquals("A Certain Kind of Test", doc.head().select("title").text()); + } } diff --git a/src/test/resources/bomtests/bom_utf8.html b/src/test/resources/bomtests/bom_utf8.html new file mode 100644 index 0000000000..0bf9680854 --- /dev/null +++ b/src/test/resources/bomtests/bom_utf8.html @@ -0,0 +1,10 @@ + + + + + OK + + +There is a UTF8 BOM at the top (before the XML decl). If not read correctly, will look like a non-joining space. + + diff --git a/src/test/resources/htmltests/comments.html b/src/test/resources/htmltests/comments.html new file mode 100644 index 0000000000..e071c6367b --- /dev/null +++ b/src/test/resources/htmltests/comments.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + A Certain Kind of Test + +

Hello

h1> +(There is a UTF8 hidden BOM at the top of this file.) + From 3c699bad0467bec4adc55666f0d02e312ab22a60 Mon Sep 17 00:00:00 2001 From: yawkat Date: Sun, 15 Apr 2018 12:37:05 +0200 Subject: [PATCH 247/774] Parse invalid unicode escapes as Windows-1252 instead [Fixes #1034] --- src/main/java/org/jsoup/parser/Tokeniser.java | 17 ++++++++++++ .../java/org/jsoup/parser/TokeniserTest.java | 26 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 1b4aa8475e..50c14378f8 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -13,6 +13,17 @@ final class Tokeniser { static final char replacementChar = '\uFFFD'; // replaces null character private static final char[] notCharRefCharsSorted = new char[]{'\t', '\n', '\r', '\f', ' ', '<', '&'}; + // Some illegal character escapes are parsed by browsers as windows-1252 instead. See issue #1034 + static final int win1252ExtensionsStart = 0x80; + static final int[] win1252Extensions = new int[] { + // we could build this manually, but Windows-1252 is not a standard java charset so that could break on + // some platforms - this table is verified with a test + 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, + 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, + }; + static { Arrays.sort(notCharRefCharsSorted); } @@ -148,6 +159,12 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in codeRef[0] = replacementChar; return codeRef; } else { + // fix illegal unicode characters to match browser behavior + if (charval >= win1252ExtensionsStart && charval < win1252ExtensionsStart + win1252Extensions.length) { + characterReferenceError("character is not a valid unicode code point"); + charval = win1252Extensions[charval - win1252ExtensionsStart]; + } + // todo: implement number replacement table // todo: check for extra illegal unicode points as parse errors codeRef[0] = charval; diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index a3da5fe5d4..52aba9dec8 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -1,5 +1,6 @@ package org.jsoup.parser; +import java.io.UnsupportedEncodingException; import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Comment; @@ -154,4 +155,29 @@ public void bufferUpInAttributeVal() { assertEquals(title, child.getWholeText()); assertEquals(title, doc.title()); } + + @Test public void cp1252Entities() { + assertEquals("\u20ac", Jsoup.parse("€").text()); + assertEquals("\u201a", Jsoup.parse("‚").text()); + assertEquals("\u20ac", Jsoup.parse("€").text()); + } + + @Test public void cp1252EntitiesProduceError() { + Parser parser = new Parser(new HtmlTreeBuilder()); + parser.setTrackErrors(10); + assertEquals("\u20ac", parser.parseInput("€", "").text()); + assertEquals(1, parser.getErrors().size()); + } + + @Test public void cp1252SubstitutionTable() throws UnsupportedEncodingException { + for (int i = 0; i < Tokeniser.win1252Extensions.length; i++) { + String s = new String(new byte[]{ (byte) (i + Tokeniser.win1252ExtensionsStart) }, "Windows-1252"); + assertEquals(1, s.length()); + + // some of these characters are illegal + if (s.charAt(0) == '\ufffd') { continue; } + + assertEquals("At: " + i, s.charAt(0), Tokeniser.win1252Extensions[i]); + } + } } From 47a7f5ab2a22a2ce526f96e0cf9f2c46511c56d9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 13:53:55 -0700 Subject: [PATCH 248/774] Keep \n on file endings --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..1657bf55c0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +core.eol lf +core.autocrlf input From e9feec90dbd3e428dc1930c3b5efbd9271160d01 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 14:09:14 -0700 Subject: [PATCH 249/774] Check that XML declarations parse OK Fixes #1015 --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/XmlTreeBuilder.java | 8 +++++--- src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java | 7 +++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index eb596b3176..bdc7de82f8 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,9 @@ jsoup changelog space, which could impact the parse tree. + * Bugfix: when parsing an invalid XML declaration, the parse would fail. + + *** Release 1.11.2 [2017-Nov-19] * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements. This enables finding text that is only marked by a "br" tag, for example. diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 10bb54a326..aec5ba7a51 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -88,9 +88,11 @@ void insert(Token.Comment commentToken) { String data = comment.getData(); if (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))) { Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri, Parser.xmlParser()); - Element el = doc.child(0); - insert = new XmlDeclaration(settings.normalizeTag(el.tagName()), data.startsWith("!")); - insert.attributes().addAll(el.attributes()); + if (doc.childNodeSize() > 0) { + Element el = doc.child(0); + insert = new XmlDeclaration(settings.normalizeTag(el.tagName()), data.startsWith("!")); + insert.attributes().addAll(el.attributes()); + } // else, we couldn't parse it as a decl, so leave as a comment } } insertNode(insert); diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index e5d6f01cb3..9b1775e948 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -220,4 +220,11 @@ public void canNormalizeCase() { assertEquals("//\n\n foo();\n//", doc.selectFirst("script").text()); } + + @Test + public void handlesDodgyXmlDecl() { + String xml = "One"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + assertEquals("One", doc.select("val").text()); + } } From f25170a35a57d8cdc63b49f74eeb1b8041516490 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 14:33:49 -0700 Subject: [PATCH 250/774] Change log for #1046 --- CHANGES | 4 ++++ src/main/java/org/jsoup/parser/Tokeniser.java | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index eb596b3176..287e74c077 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,10 @@ jsoup changelog * Improvement: support nested quotes for attribute selection queries. + * Improvement: character references from Windows-1252 that are not valid Unicode are mapped to the appropriate + Unicode replacement. + + * Bugfix: "Mark has been invalidated" exception was thrown when parsing some URLs on Android <= 6. diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 50c14378f8..fcf90d4fe0 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -14,6 +14,7 @@ final class Tokeniser { private static final char[] notCharRefCharsSorted = new char[]{'\t', '\n', '\r', '\f', ' ', '<', '&'}; // Some illegal character escapes are parsed by browsers as windows-1252 instead. See issue #1034 + // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state static final int win1252ExtensionsStart = 0x80; static final int[] win1252Extensions = new int[] { // we could build this manually, but Windows-1252 is not a standard java charset so that could break on From 834c81bb55d7ccdc0d7b158595ad7aa92db6ed8b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 14:54:11 -0700 Subject: [PATCH 251/774] Change log for #1038 --- CHANGES | 7 ++++--- src/main/java/org/jsoup/Connection.java | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 8024cd17cd..8cf62c1d37 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,7 @@ jsoup changelog *** Release 1.11.3 [PENDING] - * Improvement: accept a custom SSL socket factory - - * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are + * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. @@ -17,6 +15,9 @@ jsoup changelog * Improvement: support nested quotes for attribute selection queries. + * Improvement: accept a custom SSL socket factory in Jsoup.Connection. + + * Bugfix: "Mark has been invalidated" exception was thrown when parsing some URLs on Android <= 6. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 97bd969bd0..09c03bcfe3 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -3,6 +3,7 @@ import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; +import javax.net.ssl.SSLSocketFactory; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -12,8 +13,6 @@ import java.util.List; import java.util.Map; -import javax.net.ssl.SSLSocketFactory; - /** * A Connection provides a convenient interface to fetch content from the web, and parse them into Documents. *

@@ -609,18 +608,19 @@ interface Request extends Base { * Set TLS certificate validation. True by default. * @param value set false to ignore TLS (SSL) certificates * @deprecated as distributions (specifically Google Play) are starting to show warnings if these checks are - * disabled. + * disabled. This method will be removed in the next release. + * @see #sslSocketFactory(SSLSocketFactory) */ void validateTLSCertificates(boolean value); /** - * Get the custom SSL socket factory - * @return custom SSL socket factory + * Get the current custom SSL socket factory, if any. + * @return custom SSL socket factory if set, null otherwise */ SSLSocketFactory sslSocketFactory(); /** - * Set custom SSL socket factory. + * Set a custom SSL socket factory. * @param sslSocketFactory SSL socket factory */ void sslSocketFactory(SSLSocketFactory sslSocketFactory); From 2b1ea0633ff5d4cba5db0e202a4ce0fbdccc19db Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 15:19:58 -0700 Subject: [PATCH 252/774] [maven-release-plugin] prepare release jsoup-1.11.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4061cf22e6..bd0c1bfc8d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.11.3-SNAPSHOT + 1.11.3 jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.11.3 Jonathan Hedley From 176c173b24bf890460fe8ecb08ddf60d4fc2e195 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 15:20:09 -0700 Subject: [PATCH 253/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bd0c1bfc8d..48e4b42efd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.11.3 + 1.12.1-SNAPSHOT jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.11.3 + HEAD Jonathan Hedley From fa38b8a95414984c1ded0a3672adae95b51c1ab3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 15 Apr 2018 15:26:40 -0700 Subject: [PATCH 254/774] Release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3a129b7a85..f810ded919 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.11.3 [PENDING] +*** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. From bf6667c439a1fa29ea50d531b65633ffe150d16b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 28 Apr 2018 13:16:30 -0700 Subject: [PATCH 255/774] Removed deprecated Connection.validateTLSCertificates() method --- CHANGES | 3 + src/main/java/org/jsoup/Connection.java | 37 ----------- .../java/org/jsoup/helper/HttpConnection.java | 64 +------------------ .../org/jsoup/integration/UrlConnectTest.java | 28 -------- 4 files changed, 5 insertions(+), 127 deletions(-) diff --git a/CHANGES b/CHANGES index f810ded919..ff1d9f4064 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,8 @@ jsoup changelog +**** Release 1.12.1 [PENDING] + * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 81321e85e6..9f4fe63fdd 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -145,27 +145,6 @@ public final boolean hasBody() { */ Connection ignoreContentType(boolean ignoreContentType); - /** - * Disable/enable TLS certificates validation for HTTPS requests. - *

- * By default this is true; all - * connections over HTTPS perform normal validation of certificates, and will abort requests if the provided - * certificate does not validate. - *

- *

- * Some servers use expired, self-generated certificates; or your JDK may not - * support SNI hosts. In which case, you may want to enable this setting. - *

- *

- * Be careful and understand why you need to disable these validations. - *

- * @param value if should validate TLS (SSL) certificates. true by default. - * @return this Connection, for chaining - * @deprecated as distributions (specifically Google Play) are starting to show warnings if these checks are - * disabled. - */ - Connection validateTLSCertificates(boolean value); - /** * Set custom SSL socket factory * @param sslSocketFactory custom SSL socket factory @@ -597,22 +576,6 @@ interface Request extends Base { */ Request ignoreContentType(boolean ignoreContentType); - /** - * Get the current state of TLS (SSL) certificate validation. - * @return true if TLS cert validation enabled - * @deprecated - */ - boolean validateTLSCertificates(); - - /** - * Set TLS certificate validation. True by default. - * @param value set false to ignore TLS (SSL) certificates - * @deprecated as distributions (specifically Google Play) are starting to show warnings if these checks are - * disabled. This method will be removed in the next release. - * @see #sslSocketFactory(SSLSocketFactory) - */ - void validateTLSCertificates(boolean value); - /** * Get the current custom SSL socket factory, if any. * @return custom SSL socket factory if set, null otherwise diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index b7aa5f151f..6f6a4b250c 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -11,11 +11,8 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; @@ -35,9 +32,6 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -190,10 +184,6 @@ public Connection ignoreContentType(boolean ignoreContentType) { return this; } - public Connection validateTLSCertificates(boolean value) { - req.validateTLSCertificates(value); - return this; - } public Connection data(String key, String value) { req.data(KeyVal.create(key, value)); @@ -914,18 +904,8 @@ private static HttpURLConnection createConnection(Connection.Request req) throws conn.setConnectTimeout(req.timeout()); conn.setReadTimeout(req.timeout() / 2); // gets reduced after connection is made and status is read - if (conn instanceof HttpsURLConnection) { - SSLSocketFactory socketFactory = req.sslSocketFactory(); - - if (socketFactory != null) { - ((HttpsURLConnection) conn).setSSLSocketFactory(socketFactory); - } else if (!req.validateTLSCertificates()) { - initUnSecureTSL(); - ((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory); - ((HttpsURLConnection)conn).setHostnameVerifier(getInsecureVerifier()); - } - } - + if (req.sslSocketFactory() != null && conn instanceof HttpsURLConnection) + ((HttpsURLConnection) conn).setSSLSocketFactory(req.sslSocketFactory()); if (req.method().hasBody()) conn.setDoOutput(true); if (req.cookies().size() > 0) @@ -957,7 +937,6 @@ private void safeClose() { * Instantiate Hostname Verifier that does nothing. * This is used for connections with disabled SSL certificates validation. * - * * @return Hostname Verifier that does nothing and accepts all hostnames */ private static HostnameVerifier getInsecureVerifier() { @@ -968,45 +947,6 @@ public boolean verify(String urlHostName, SSLSession session) { }; } - /** - * Initialise Trust manager that does not validate certificate chains and - * add it to current SSLContext. - *

- * please not that this method will only perform action if sslSocketFactory is not yet - * instantiated. - * - * @throws IOException on SSL init errors - */ - private static synchronized void initUnSecureTSL() throws IOException { - if (sslSocketFactory == null) { - // Create a trust manager that does not validate certificate chains - final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - - public void checkClientTrusted(final X509Certificate[] chain, final String authType) { - } - - public void checkServerTrusted(final X509Certificate[] chain, final String authType) { - } - - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }}; - - // Install the all-trusting trust manager - final SSLContext sslContext; - try { - sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - // Create an ssl socket factory with our all-trusting manager - sslSocketFactory = sslContext.getSocketFactory(); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IOException("Can't create unsecure trust manager"); - } - } - - } - // set up url, method, header, cookies private void setupFromConnection(HttpURLConnection conn, Connection.Response previousResponse) throws IOException { method = Method.valueOf(conn.getRequestMethod()); diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 6a4a20fb9d..fa5facdb77 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -306,34 +306,6 @@ public void testSNIFail() throws Exception { Jsoup.connect(url).execute(); } - /** - * Verify that requests to websites with SNI pass - *

- * NB! this test is FAILING right now on jdk 1.6 - * - * @throws Exception - */ - @Test - public void testSNIPass() throws Exception { - String url = WEBSITE_WITH_SNI; - Connection.Response defaultRes = Jsoup.connect(url).validateTLSCertificates(false).execute(); - assertEquals(defaultRes.statusCode(), 200); - } - - /** - * Verify that security disabling feature works properly. - *

- * 1. disable security checks and call the same url to verify that content is consumed correctly - * - * @throws Exception - */ - @Test - public void testUnsafePass() throws Exception { - String url = WEBSITE_WITH_INVALID_CERTIFICATE; - Connection.Response defaultRes = Jsoup.connect(url).validateTLSCertificates(false).execute(); - assertEquals(defaultRes.statusCode(), 200); - } - @Test public void shouldWorkForCharsetInExtraAttribute() throws IOException { Connection.Response res = Jsoup.connect("https://www.creditmutuel.com/groupe/fr/").execute(); From 850a9cc02fb72cc450d1a9dc41912fa80fee9020 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 28 Apr 2018 18:51:43 -0700 Subject: [PATCH 256/774] Maintain the Parser used when parsing a Document Allows appropriate fragment parsing as XML or HTML, and remembers case sensitivity choice. Fixes #769 --- CHANGES | 4 +++ src/main/java/org/jsoup/nodes/Document.java | 22 ++++++++++++ src/main/java/org/jsoup/nodes/Element.java | 13 +++---- src/main/java/org/jsoup/nodes/Node.java | 13 +++++-- .../org/jsoup/parser/HtmlTreeBuilder.java | 12 +++---- .../java/org/jsoup/parser/ParseSettings.java | 4 +-- src/main/java/org/jsoup/parser/Parser.java | 28 ++++++++------- .../java/org/jsoup/parser/TreeBuilder.java | 20 ++++++----- .../java/org/jsoup/parser/XmlTreeBuilder.java | 36 ++++++++++++------- .../java/org/jsoup/nodes/ElementTest.java | 4 +-- .../java/org/jsoup/parser/HtmlParserTest.java | 16 +++++++++ .../org/jsoup/parser/XmlTreeBuilderTest.java | 10 ++++++ 12 files changed, 129 insertions(+), 53 deletions(-) diff --git a/CHANGES b/CHANGES index ff1d9f4064..91697454ae 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,10 @@ jsoup changelog **** Release 1.12.1 [PENDING] * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). + * Improvement: documents now remember their parser, so when later manipulating them, the correct HTML or XML tree + builder is reused, as are the parser settings like case sensitivity. + + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index b7143bbff5..9d6a4aee32 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -3,6 +3,7 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; +import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; @@ -17,6 +18,7 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class Document extends Element { private OutputSettings outputSettings = new OutputSettings(); + private Parser parser; // the parser used to parse this document private QuirksMode quirksMode = QuirksMode.noQuirks; private String location; private boolean updateMetaCharset = false; @@ -41,6 +43,7 @@ public static Document createShell(String baseUri) { Validate.notNull(baseUri); Document doc = new Document(baseUri); + doc.parser = doc.parser(); Element html = doc.appendElement("html"); html.appendElement("head"); html.appendElement("body"); @@ -573,4 +576,23 @@ public Document quirksMode(QuirksMode quirksMode) { this.quirksMode = quirksMode; return this; } + + /** + * Get the parser that was used to parse this document. + * @return the parser + */ + public Parser parser() { + return parser; + } + + /** + * Set the parser used to create this document. This parser is then used when further parsing within this document + * is required. + * @param parser the configured parser to use when further parsing is required for this document. + * @return this document, for chaining. + */ + public Document parser(Parser parser) { + this.parser = parser; + return this; + } } diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 1f980e1c4e..18da685797 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -4,7 +4,6 @@ import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; -import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Collector; import org.jsoup.select.Elements; @@ -141,7 +140,7 @@ public String tagName() { */ public Element tagName(String tagName) { Validate.notEmpty(tagName, "Tag name must not be empty."); - tag = Tag.valueOf(tagName, ParseSettings.preserveCase); // preserve the requested tag case + tag = Tag.valueOf(tagName, getParser().settings()); // maintains the case option of the original parse return this; } @@ -483,7 +482,7 @@ public Element insertChildren(int index, Node... children) { * {@code parent.appendElement("h1").attr("id", "header").text("Welcome");} */ public Element appendElement(String tagName) { - Element child = new Element(Tag.valueOf(tagName), baseUri()); + Element child = new Element(Tag.valueOf(tagName, getParser().settings()), baseUri()); appendChild(child); return child; } @@ -496,7 +495,7 @@ public Element appendElement(String tagName) { * {@code parent.prependElement("h1").attr("id", "header").text("Welcome");} */ public Element prependElement(String tagName) { - Element child = new Element(Tag.valueOf(tagName), baseUri()); + Element child = new Element(Tag.valueOf(tagName, getParser().settings()), baseUri()); prependChild(child); return child; } @@ -535,8 +534,7 @@ public Element prependText(String text) { */ public Element append(String html) { Validate.notNull(html); - - List nodes = Parser.parseFragment(html, this, baseUri()); + List nodes = getParser().parseFragmentInput(html, this, baseUri()); addChildren(nodes.toArray(new Node[nodes.size()])); return this; } @@ -549,8 +547,7 @@ public Element append(String html) { */ public Element prepend(String html) { Validate.notNull(html); - - List nodes = Parser.parseFragment(html, this, baseUri()); + List nodes = getParser().parseFragmentInput(html, this, baseUri()); addChildren(0, nodes.toArray(new Node[nodes.size()])); return this; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 37467a7c1c..d7ad20ad86 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -3,6 +3,7 @@ import org.jsoup.SerializationException; import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.parser.HtmlTreeBuilder; import org.jsoup.parser.Parser; import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; @@ -77,12 +78,13 @@ else if (attributeKey.startsWith("abs:")) /** * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is - * case insensitive. + * case insensitive. The key will be set with case sensitivity as set in the parser settings. * @param attributeKey The attribute key. * @param attributeValue The attribute value. * @return this (for chaining) */ public Node attr(String attributeKey, String attributeValue) { + attributeKey = getParser().settings().normalizeAttribute(attributeKey); attributes().putIgnoreCase(attributeKey, attributeValue); return this; } @@ -332,7 +334,7 @@ private void addSiblingHtml(int index, String html) { Validate.notNull(parentNode); Element context = parent() instanceof Element ? (Element) parent() : null; - List nodes = Parser.parseFragment(html, context, baseUri()); + List nodes = getParser().parseFragmentInput(html, context, baseUri()); parentNode.addChildren(index, nodes.toArray(new Node[nodes.size()])); } @@ -345,7 +347,7 @@ public Node wrap(String html) { Validate.notEmpty(html); Element context = parent() instanceof Element ? (Element) parent() : null; - List wrapChildren = Parser.parseFragment(html, context, baseUri()); + List wrapChildren = getParser().parseFragmentInput(html, context, baseUri()); Node wrapNode = wrapChildren.get(0); if (wrapNode == null || !(wrapNode instanceof Element)) // nothing to wrap with; noop return null; @@ -579,6 +581,11 @@ Document.OutputSettings getOutputSettings() { return owner != null ? owner.outputSettings() : (new Document("")).outputSettings(); } + Parser getParser() { + Document doc = ownerDocument(); + return doc != null && doc.parser() != null ? doc.parser() : new Parser(new HtmlTreeBuilder()); + } + /** Get the outer HTML of this node. @param accum accumulator to place HTML into diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index efc86bdac1..455bd6c79f 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -61,8 +61,8 @@ ParseSettings defaultSettings() { } @Override - protected void initialiseParse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { - super.initialiseParse(input, baseUri, errors, settings); + protected void initialiseParse(Reader input, String baseUri, Parser parser) { + super.initialiseParse(input, baseUri, parser); // this is a bit mucky. todo - probably just create new parser objects to ensure all reset. state = HtmlTreeBuilderState.Initial; @@ -79,10 +79,10 @@ protected void initialiseParse(Reader input, String baseUri, ParseErrorList erro fragmentParsing = false; } - List parseFragment(String inputFragment, Element context, String baseUri, ParseErrorList errors, ParseSettings settings) { + List parseFragment(String inputFragment, Element context, String baseUri, Parser parser) { // context may be null state = HtmlTreeBuilderState.Initial; - initialiseParse(new StringReader(inputFragment), baseUri, errors, settings); + initialiseParse(new StringReader(inputFragment), baseUri, parser); contextElement = context; fragmentParsing = true; Element root = null; @@ -190,8 +190,8 @@ boolean isFragmentParsing() { } void error(HtmlTreeBuilderState state) { - if (errors.canAddError()) - errors.add(new ParseError(reader.pos(), "Unexpected token [%s] when in state [%s]", currentToken.tokenType(), state)); + if (parser.getErrors().canAddError()) + parser.getErrors().add(new ParseError(reader.pos(), "Unexpected token [%s] when in state [%s]", currentToken.tokenType(), state)); } Element insert(Token.StartTag startTag) { diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index 25a9b86e99..71bb4ce1ae 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -35,14 +35,14 @@ public ParseSettings(boolean tag, boolean attribute) { preserveAttributeCase = attribute; } - String normalizeTag(String name) { + public String normalizeTag(String name) { name = name.trim(); if (!preserveTagCase) name = lowerCase(name); return name; } - String normalizeAttribute(String name) { + public String normalizeAttribute(String name) { name = name.trim(); if (!preserveAttributeCase) name = lowerCase(name); diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index f3e99e1d80..919c9db35c 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -16,7 +16,6 @@ public class Parser { private static final int DEFAULT_MAX_ERRORS = 0; // by default, error tracking is disabled. private TreeBuilder treeBuilder; - private int maxErrors = DEFAULT_MAX_ERRORS; private ParseErrorList errors; private ParseSettings settings; @@ -27,18 +26,20 @@ public class Parser { public Parser(TreeBuilder treeBuilder) { this.treeBuilder = treeBuilder; settings = treeBuilder.defaultSettings(); + errors = ParseErrorList.noTracking(); } public Document parseInput(String html, String baseUri) { - errors = isTrackErrors() ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking(); - return treeBuilder.parse(new StringReader(html), baseUri, errors, settings); + return treeBuilder.parse(new StringReader(html), baseUri, this); } public Document parseInput(Reader inputHtml, String baseUri) { - errors = isTrackErrors() ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking(); - return treeBuilder.parse(inputHtml, baseUri, errors, settings); + return treeBuilder.parse(inputHtml, baseUri, this); } + public List parseFragmentInput(String fragment, Element context, String baseUri) { + return treeBuilder.parseFragment(fragment, context, baseUri, this); + } // gets & sets /** * Get the TreeBuilder currently in use. @@ -55,6 +56,7 @@ public TreeBuilder getTreeBuilder() { */ public Parser setTreeBuilder(TreeBuilder treeBuilder) { this.treeBuilder = treeBuilder; + treeBuilder.parser = this; return this; } @@ -63,7 +65,7 @@ public Parser setTreeBuilder(TreeBuilder treeBuilder) { * @return current track error state. */ public boolean isTrackErrors() { - return maxErrors > 0; + return errors.getMaxSize() > 0; } /** @@ -72,7 +74,7 @@ public boolean isTrackErrors() { * @return this, for chaining */ public Parser setTrackErrors(int maxErrors) { - this.maxErrors = maxErrors; + errors = maxErrors > 0 ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking(); return this; } @@ -80,7 +82,7 @@ public Parser setTrackErrors(int maxErrors) { * Retrieve the parse errors, if any, from the last parse. * @return list of parse errors, up to the size of the maximum errors tracked. */ - public List getErrors() { + public ParseErrorList getErrors() { return errors; } @@ -104,7 +106,7 @@ public ParseSettings settings() { */ public static Document parse(String html, String baseUri) { TreeBuilder treeBuilder = new HtmlTreeBuilder(); - return treeBuilder.parse(new StringReader(html), baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); + return treeBuilder.parse(new StringReader(html), baseUri, new Parser(treeBuilder)); } /** @@ -119,7 +121,7 @@ public static Document parse(String html, String baseUri) { */ public static List parseFragment(String fragmentHtml, Element context, String baseUri) { HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder(); - return treeBuilder.parseFragment(fragmentHtml, context, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); + return treeBuilder.parseFragment(fragmentHtml, context, baseUri, new Parser(treeBuilder)); } /** @@ -135,7 +137,9 @@ public static List parseFragment(String fragmentHtml, Element context, Str */ public static List parseFragment(String fragmentHtml, Element context, String baseUri, ParseErrorList errorList) { HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder(); - return treeBuilder.parseFragment(fragmentHtml, context, baseUri, errorList, treeBuilder.defaultSettings()); + Parser parser = new Parser(treeBuilder); + parser.errors = errorList; + return treeBuilder.parseFragment(fragmentHtml, context, baseUri, parser); } /** @@ -147,7 +151,7 @@ public static List parseFragment(String fragmentHtml, Element context, Str */ public static List parseXmlFragment(String fragmentXml, String baseUri) { XmlTreeBuilder treeBuilder = new XmlTreeBuilder(); - return treeBuilder.parseFragment(fragmentXml, baseUri, ParseErrorList.noTracking(), treeBuilder.defaultSettings()); + return treeBuilder.parseFragment(fragmentXml, baseUri, new Parser(treeBuilder)); } /** diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 6833043839..75dd91a786 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -4,48 +4,52 @@ import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; import java.io.Reader; import java.util.ArrayList; +import java.util.List; /** * @author Jonathan Hedley */ abstract class TreeBuilder { + protected Parser parser; CharacterReader reader; Tokeniser tokeniser; protected Document doc; // current doc we are building into protected ArrayList stack; // the stack of open elements protected String baseUri; // current base uri, for creating new elements protected Token currentToken; // currentToken is used only for error tracking. - protected ParseErrorList errors; // null when not tracking errors protected ParseSettings settings; private Token.StartTag start = new Token.StartTag(); // start tag to process private Token.EndTag end = new Token.EndTag(); - abstract ParseSettings defaultSettings(); - protected void initialiseParse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { + protected void initialiseParse(Reader input, String baseUri, Parser parser) { Validate.notNull(input, "String input must not be null"); Validate.notNull(baseUri, "BaseURI must not be null"); doc = new Document(baseUri); - this.settings = settings; + doc.parser(parser); + this.parser = parser; + settings = parser.settings(); reader = new CharacterReader(input); - this.errors = errors; currentToken = null; - tokeniser = new Tokeniser(reader, errors); + tokeniser = new Tokeniser(reader, parser.getErrors()); stack = new ArrayList<>(32); this.baseUri = baseUri; } - Document parse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { - initialiseParse(input, baseUri, errors, settings); + Document parse(Reader input, String baseUri, Parser parser) { + initialiseParse(input, baseUri, parser); runParser(); return doc; } + abstract List parseFragment(String inputFragment, Element context, String baseUri, Parser parser); + protected void runParser() { while (true) { Token token = tokeniser.read(); diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index aec5ba7a51..f4b782e697 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -2,7 +2,14 @@ import org.jsoup.Jsoup; import org.jsoup.helper.Validate; -import org.jsoup.nodes.*; +import org.jsoup.nodes.CDataNode; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.DocumentType; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; +import org.jsoup.nodes.XmlDeclaration; import java.io.Reader; import java.io.StringReader; @@ -20,19 +27,19 @@ ParseSettings defaultSettings() { return ParseSettings.preserveCase; } - Document parse(Reader input, String baseUri) { - return parse(input, baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase); + @Override + protected void initialiseParse(Reader input, String baseUri, Parser parser) { + super.initialiseParse(input, baseUri, parser); + stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) + doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); } - Document parse(String input, String baseUri) { - return parse(new StringReader(input), baseUri, ParseErrorList.noTracking(), ParseSettings.preserveCase); + Document parse(Reader input, String baseUri) { + return parse(input, baseUri, new Parser(this)); } - @Override - protected void initialiseParse(Reader input, String baseUri, ParseErrorList errors, ParseSettings settings) { - super.initialiseParse(input, baseUri, errors, settings); - stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) - doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + Document parse(String input, String baseUri) { + return parse(new StringReader(input), baseUri, new Parser(this)); } @Override @@ -137,9 +144,14 @@ private void popStackToClose(Token.EndTag endTag) { } } - List parseFragment(String inputFragment, String baseUri, ParseErrorList errors, ParseSettings settings) { - initialiseParse(new StringReader(inputFragment), baseUri, errors, settings); + + List parseFragment(String inputFragment, String baseUri, Parser parser) { + initialiseParse(new StringReader(inputFragment), baseUri, parser); runParser(); return doc.childNodes(); } + + List parseFragment(String inputFragment, Element context, String baseUri, Parser parser) { + return parseFragment(inputFragment, baseUri, parser); + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 39f452b01f..70a58f06a3 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -384,8 +384,8 @@ public class ElementTest { Element div = doc.getElementById("1"); div.appendElement("p").text("there"); div.appendElement("P").attr("CLASS", "second").text("now"); - // manually specifying tag and attributes should now preserve case, regardless of parse mode - assertEquals("

Hello

there

now

", + // manually specifying tag and attributes should maintain case based on parser settings + assertEquals("

Hello

there

now

", TextUtil.stripNewlines(doc.html())); // check sibling index (with short circuit on reindexChildren): diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 4032e7c1b9..f583ed6182 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1062,6 +1062,10 @@ public void testInvalidTableContents() throws IOException { String html = "
One
"; Document doc = Jsoup.parse(html); assertEquals("
One
", StringUtil.normaliseWhitespace(doc.outerHtml())); + + Element div = doc.selectFirst("#1"); + div.after("One"); + assertEquals("One", TextUtil.stripNewlines(div.nextElementSibling().outerHtml())); } @Test public void canPreserveTagCase() { @@ -1069,6 +1073,10 @@ public void testInvalidTableContents() throws IOException { parser.settings(new ParseSettings(true, false)); Document doc = parser.parseInput("
", ""); assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); + + Element div = doc.selectFirst("#1"); + div.after("One"); + assertEquals("One", TextUtil.stripNewlines(div.nextElementSibling().outerHtml())); } @Test public void canPreserveAttributeCase() { @@ -1076,6 +1084,10 @@ public void testInvalidTableContents() throws IOException { parser.settings(new ParseSettings(false, true)); Document doc = parser.parseInput("
", ""); assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); + + Element div = doc.selectFirst("#1"); + div.after("One"); + assertEquals("One", TextUtil.stripNewlines(div.nextElementSibling().outerHtml())); } @Test public void canPreserveBothCase() { @@ -1083,6 +1095,10 @@ public void testInvalidTableContents() throws IOException { parser.settings(new ParseSettings(true, true)); Document doc = parser.parseInput("
", ""); assertEquals("
", StringUtil.normaliseWhitespace(doc.outerHtml())); + + Element div = doc.selectFirst("#1"); + div.after("One"); + assertEquals("One", TextUtil.stripNewlines(div.nextElementSibling().outerHtml())); } @Test public void handlesControlCodeInAttributeName() { diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 9b1775e948..08edc75c8a 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -9,6 +9,7 @@ import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; import org.jsoup.nodes.XmlDeclaration; +import org.jsoup.select.Elements; import org.junit.Ignore; import org.junit.Test; @@ -184,6 +185,15 @@ public void preservesCaseByDefault() { assertEquals("OneCheck", TextUtil.stripNewlines(doc.html())); } + @Test + public void appendPreservesCaseByDefault() { + String xml = "One"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + Elements one = doc.select("One"); + one.append("Two"); + assertEquals("OneTwo", TextUtil.stripNewlines(doc.html())); + } + @Test public void canNormalizeCase() { String xml = "Check"; From e38dfd44829e13ee83fd62bfe937580f5a998c11 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 28 Apr 2018 19:58:51 -0700 Subject: [PATCH 257/774] Detect charset from pseudo XML declaration when in HTML parser mode Fixes #1009 --- CHANGES | 4 +++ src/main/java/org/jsoup/helper/DataUtil.java | 20 ++++++++++--- src/main/java/org/jsoup/nodes/Comment.java | 28 +++++++++++++++++++ .../java/org/jsoup/parser/XmlTreeBuilder.java | 16 ++++------- .../java/org/jsoup/helper/DataUtilTest.java | 13 +++++++++ 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 91697454ae..92ecc3ad1f 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,10 @@ jsoup changelog builder is reused, as are the parser settings like case sensitivity. + * Improvement: Jsoup now detects the character set of the input if specified in an XML Declaration, when using the + HTML parser. Previously that only happened when the XML parser was specified. + + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 5f276eec91..3aaa0ed3f1 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -2,8 +2,10 @@ import org.jsoup.UncheckedIOException; import org.jsoup.internal.ConstrainableInputStream; +import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; import org.jsoup.nodes.XmlDeclaration; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; @@ -127,10 +129,20 @@ static Document parseInputStream(InputStream input, String charsetName, String b } // look for - if (foundCharset == null && doc.childNodeSize() > 0 && doc.childNode(0) instanceof XmlDeclaration) { - XmlDeclaration prolog = (XmlDeclaration) doc.childNode(0); - if (prolog.name().equals("xml")) - foundCharset = prolog.attr("encoding"); + if (foundCharset == null && doc.childNodeSize() > 0) { + Node first = doc.childNode(0); + XmlDeclaration decl = null; + if (first instanceof XmlDeclaration) + decl = (XmlDeclaration) first; + else if (first instanceof Comment) { + Comment comment = (Comment) first; + if (comment.isXmlDeclaration()) + decl = comment.asXmlDeclaration(); + } + if (decl != null) { + if (decl.name().equalsIgnoreCase("xml")) + foundCharset = decl.attr("encoding"); + } } foundCharset = validateCharset(foundCharset); if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharset)) { // need to re-decode. (case insensitive check here to match how validate works) diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index e467a39084..021d0d1c53 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -1,5 +1,8 @@ package org.jsoup.nodes; +import org.jsoup.Jsoup; +import org.jsoup.parser.Parser; + import java.io.IOException; /** @@ -54,4 +57,29 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} public String toString() { return outerHtml(); } + + /** + * Check if this comment looks like an XML Declaration. + * @return true if it looks like, maybe, it's an XML Declaration. + */ + public boolean isXmlDeclaration() { + String data = getData(); + return (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))); + } + + /** + * Attempt to cast this comment to an XML Declaration note. + * @return an XML declaration if it could be parsed as one, null otherwise. + */ + public XmlDeclaration asXmlDeclaration() { + String data = getData(); + Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri(), Parser.xmlParser()); + XmlDeclaration decl = null; + if (doc.childNodeSize() > 0) { + Element el = doc.child(0); + decl = new XmlDeclaration(doc.getParser().settings().normalizeTag(el.tagName()), data.startsWith("!")); + decl.attributes().addAll(el.attributes()); + } + return decl; + } } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index f4b782e697..87cdc3728d 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -1,6 +1,5 @@ package org.jsoup.parser; -import org.jsoup.Jsoup; import org.jsoup.helper.Validate; import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; @@ -90,17 +89,12 @@ Element insert(Token.StartTag startTag) { void insert(Token.Comment commentToken) { Comment comment = new Comment(commentToken.getData()); Node insert = comment; - if (commentToken.bogus) { // xml declarations are emitted as bogus comments (which is right for html, but not xml) + if (commentToken.bogus && comment.isXmlDeclaration()) { + // xml declarations are emitted as bogus comments (which is right for html, but not xml) // so we do a bit of a hack and parse the data as an element to pull the attributes out - String data = comment.getData(); - if (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))) { - Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri, Parser.xmlParser()); - if (doc.childNodeSize() > 0) { - Element el = doc.child(0); - insert = new XmlDeclaration(settings.normalizeTag(el.tagName()), data.startsWith("!")); - insert.attributes().addAll(el.attributes()); - } // else, we couldn't parse it as a decl, so leave as a comment - } + XmlDeclaration decl = comment.asXmlDeclaration(); // else, we couldn't parse it as a decl, so leave as a comment + if (decl != null) + insert = decl; } insertNode(insert); } diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 7021676d4e..c1f45d4185 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -164,4 +164,17 @@ public void supportsUTF8BOM() throws IOException { Document doc = Jsoup.parse(in, null, "http://example.com"); assertEquals("OK", doc.head().select("title").text()); } + + @Test + public void supportsXmlCharsetDeclaration() throws IOException { + String encoding = "iso-8859-1"; + InputStream soup = new ByteArrayInputStream(( + "" + + "" + + "Hellö Wörld!" + ).getBytes(encoding)); + + Document doc = Jsoup.parse(soup, null, ""); + assertEquals("Hellö Wörld!", doc.body().text()); + } } From d2fb53fd1ca0e06d877c8e9b8cd5da897c0d6618 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 10:01:41 -0700 Subject: [PATCH 258/774] Para move --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f39167e104..a2ed057b0b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ jsoup is designed to deal with all varieties of HTML found in the wild; from pri See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API documentation](https://jsoup.org/apidocs/). ## Example -Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html) ([online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g), [full source](https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/examples/Wikipedia.java)): +Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html): ```java Document doc = Jsoup.connect("http://en.wikipedia.org/").get(); @@ -27,6 +27,7 @@ for (Element headline : newsHeadlines) { headline.attr("title"), headline.absUrl("href")); } ``` +[Online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g), [full source](https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/examples/Wikipedia.java). ## Open source jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available at [GitHub](https://github.com/jhy/jsoup/tree/master/src/main/java/org/jsoup). From f1b885d238d8576d91d71a09f8658358dd846921 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 11:43:19 -0700 Subject: [PATCH 259/774] Spring cleaning --- CHANGES | 3 ++ src/main/java/org/jsoup/helper/DataUtil.java | 18 --------- .../java/org/jsoup/helper/HttpConnection.java | 26 ------------- src/main/java/org/jsoup/nodes/Comment.java | 2 +- src/main/java/org/jsoup/nodes/Element.java | 34 ++++++----------- src/main/java/org/jsoup/nodes/Node.java | 37 ++++++++----------- src/main/java/org/jsoup/nodes/NodeUtils.java | 27 ++++++++++++++ .../org/jsoup/nodes/PseudoTextElement.java | 6 +-- .../org/jsoup/parser/HtmlTreeBuilder.java | 4 -- src/main/java/org/jsoup/parser/Parser.java | 2 - src/main/java/org/jsoup/safety/Whitelist.java | 11 ++---- 11 files changed, 64 insertions(+), 106 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/NodeUtils.java diff --git a/CHANGES b/CHANGES index 92ecc3ad1f..6f2e2aa344 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,9 @@ jsoup changelog **** Release 1.12.1 [PENDING] * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). + * Change: some internal methods have been rearranged; if you extended any of the Jsoup internals you may need to make + updates. + * Improvement: documents now remember their parser, so when later manipulating them, the correct HTML or XML tree builder is reused, as are the parser settings like case sensitivity. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 3aaa0ed3f1..0e8e819481 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -17,7 +17,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.RandomAccessFile; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -187,23 +186,6 @@ public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) thr return input.readToByteBuffer(maxSize); } - static ByteBuffer readToByteBuffer(InputStream inStream) throws IOException { - return readToByteBuffer(inStream, 0); - } - - static ByteBuffer readFileToByteBuffer(File file) throws IOException { - RandomAccessFile randomAccessFile = null; - try { - randomAccessFile = new RandomAccessFile(file, "r"); - byte[] bytes = new byte[(int) randomAccessFile.length()]; - randomAccessFile.readFully(bytes); - return ByteBuffer.wrap(bytes); - } finally { - if (randomAccessFile != null) - randomAccessFile.close(); - } - } - static ByteBuffer emptyByteBuffer() { return ByteBuffer.allocate(0); } diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 6f6a4b250c..b363a98435 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -9,9 +9,7 @@ import org.jsoup.parser.Parser; import org.jsoup.parser.TokenQueue; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedInputStream; import java.io.BufferedWriter; @@ -542,7 +540,6 @@ public static class Request extends HttpConnection.Base impl private boolean ignoreContentType = false; private Parser parser; private boolean parserDefined = false; // called parser(...) vs initialized in ctor - private boolean validateTSLCertificates = true; private String postDataCharset = DataUtil.defaultCharset; private SSLSocketFactory sslSocketFactory; @@ -604,14 +601,6 @@ public boolean ignoreHttpErrors() { return ignoreHttpErrors; } - public boolean validateTLSCertificates() { - return validateTSLCertificates; - } - - public void validateTLSCertificates(boolean value) { - validateTSLCertificates = value; - } - public SSLSocketFactory sslSocketFactory() { return sslSocketFactory; } @@ -677,7 +666,6 @@ public String postDataCharset() { public static class Response extends HttpConnection.Base implements Connection.Response { private static final int MAX_REDIRECTS = 20; - private static SSLSocketFactory sslSocketFactory; private static final String LOCATION = "Location"; private int statusCode; private String statusMessage; @@ -933,20 +921,6 @@ private void safeClose() { } } - /** - * Instantiate Hostname Verifier that does nothing. - * This is used for connections with disabled SSL certificates validation. - * - * @return Hostname Verifier that does nothing and accepts all hostnames - */ - private static HostnameVerifier getInsecureVerifier() { - return new HostnameVerifier() { - public boolean verify(String urlHostName, SSLSession session) { - return true; - } - }; - } - // set up url, method, header, cookies private void setupFromConnection(HttpURLConnection conn, Connection.Response previousResponse) throws IOException { method = Method.valueOf(conn.getRequestMethod()); diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index 021d0d1c53..d0669fef4f 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -77,7 +77,7 @@ public XmlDeclaration asXmlDeclaration() { XmlDeclaration decl = null; if (doc.childNodeSize() > 0) { Element el = doc.child(0); - decl = new XmlDeclaration(doc.getParser().settings().normalizeTag(el.tagName()), data.startsWith("!")); + decl = new XmlDeclaration(NodeUtils.parser(doc).settings().normalizeTag(el.tagName()), data.startsWith("!")); decl.attributes().addAll(el.attributes()); } return decl; diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 18da685797..a966b600b5 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -140,7 +140,7 @@ public String tagName() { */ public Element tagName(String tagName) { Validate.notEmpty(tagName, "Tag name must not be empty."); - tag = Tag.valueOf(tagName, getParser().settings()); // maintains the case option of the original parse + tag = Tag.valueOf(tagName, NodeUtils.parser(this).settings()); // maintains the case option of the original parse return this; } @@ -482,7 +482,7 @@ public Element insertChildren(int index, Node... children) { * {@code parent.appendElement("h1").attr("id", "header").text("Welcome");} */ public Element appendElement(String tagName) { - Element child = new Element(Tag.valueOf(tagName, getParser().settings()), baseUri()); + Element child = new Element(Tag.valueOf(tagName, NodeUtils.parser(this).settings()), baseUri()); appendChild(child); return child; } @@ -495,7 +495,7 @@ public Element appendElement(String tagName) { * {@code parent.prependElement("h1").attr("id", "header").text("Welcome");} */ public Element prependElement(String tagName) { - Element child = new Element(Tag.valueOf(tagName, getParser().settings()), baseUri()); + Element child = new Element(Tag.valueOf(tagName, NodeUtils.parser(this).settings()), baseUri()); prependChild(child); return child; } @@ -534,7 +534,7 @@ public Element prependText(String text) { */ public Element append(String html) { Validate.notNull(html); - List nodes = getParser().parseFragmentInput(html, this, baseUri()); + List nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri()); addChildren(nodes.toArray(new Node[nodes.size()])); return this; } @@ -547,7 +547,7 @@ public Element append(String html) { */ public Element prepend(String html) { Validate.notNull(html); - List nodes = getParser().parseFragmentInput(html, this, baseUri()); + List nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri()); addChildren(0, nodes.toArray(new Node[nodes.size()])); return this; } @@ -733,7 +733,8 @@ public Element lastElementSibling() { } private static int indexInList(Element search, List elements) { - for (int i = 0; i < elements.size(); i++) { + final int size = elements.size(); + for (int i = 0; i < size; i++) { if (elements.get(i) == search) return i; } @@ -1114,7 +1115,7 @@ private static void appendWhitespaceIfBr(Element element, StringBuilder accum) { static boolean preserveWhitespace(Node node) { // looks only at this element and five levels up, to prevent recursion & needless stack searches - if (node != null && node instanceof Element) { + if (node instanceof Element) { Element el = (Element) node; int i = 0; do { @@ -1396,21 +1397,14 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) thr public String html() { StringBuilder accum = StringUtil.stringBuilder(); html(accum); - return getOutputSettings().prettyPrint() ? accum.toString().trim() : accum.toString(); + return NodeUtils.outputSettings(this).prettyPrint() ? accum.toString().trim() : accum.toString(); } - private void html(StringBuilder accum) { - for (Node node : childNodes) - node.outerHtml(accum); - } - - /** - * {@inheritDoc} - */ @Override public T html(T appendable) { - for (Node node : childNodes) - node.outerHtml(appendable); + final int size = childNodes.size(); + for (int i = 0; i < size; i++) + childNodes.get(i).outerHtml(appendable); return appendable; } @@ -1427,10 +1421,6 @@ public Element html(String html) { return this; } - public String toString() { - return outerHtml(); - } - @Override public Element clone() { return (Element) super.clone(); diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index d7ad20ad86..67d9496b8e 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -3,8 +3,6 @@ import org.jsoup.SerializationException; import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; -import org.jsoup.parser.HtmlTreeBuilder; -import org.jsoup.parser.Parser; import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; @@ -84,7 +82,7 @@ else if (attributeKey.startsWith("abs:")) * @return this (for chaining) */ public Node attr(String attributeKey, String attributeValue) { - attributeKey = getParser().settings().normalizeAttribute(attributeKey); + attributeKey = NodeUtils.parser(this).settings().normalizeAttribute(attributeKey); attributes().putIgnoreCase(attributeKey, attributeValue); return this; } @@ -334,7 +332,7 @@ private void addSiblingHtml(int index, String html) { Validate.notNull(parentNode); Element context = parent() instanceof Element ? (Element) parent() : null; - List nodes = getParser().parseFragmentInput(html, context, baseUri()); + List nodes = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); parentNode.addChildren(index, nodes.toArray(new Node[nodes.size()])); } @@ -347,9 +345,9 @@ public Node wrap(String html) { Validate.notEmpty(html); Element context = parent() instanceof Element ? (Element) parent() : null; - List wrapChildren = getParser().parseFragmentInput(html, context, baseUri()); + List wrapChildren = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); Node wrapNode = wrapChildren.get(0); - if (wrapNode == null || !(wrapNode instanceof Element)) // nothing to wrap with; noop + if (!(wrapNode instanceof Element)) // nothing to wrap with; noop return null; Element wrap = (Element) wrapNode; @@ -562,28 +560,19 @@ public Node filter(NodeFilter nodeFilter) { } /** - Get the outer HTML of this node. - @return HTML + Get the outer HTML of this node. For example, on a {@code p} element, may return {@code

Para

}. + @return outer HTML + @see Element#html() + @see Element#text() */ public String outerHtml() { - StringBuilder accum = new StringBuilder(128); + StringBuilder accum = StringUtil.stringBuilder(); outerHtml(accum); return accum.toString(); } protected void outerHtml(Appendable accum) { - NodeTraversor.traverse(new OuterHtmlVisitor(accum, getOutputSettings()), this); - } - - // if this node has no document (or parent), retrieve the default output settings - Document.OutputSettings getOutputSettings() { - Document owner = ownerDocument(); - return owner != null ? owner.outputSettings() : (new Document("")).outputSettings(); - } - - Parser getParser() { - Document doc = ownerDocument(); - return doc != null && doc.parser() != null ? doc.parser() : new Parser(new HtmlTreeBuilder()); + NodeTraversor.traverse(new OuterHtmlVisitor(accum, NodeUtils.outputSettings(this)), this); } /** @@ -606,6 +595,11 @@ public T html(T appendable) { return appendable; } + /** + * Gets this node's outer HTML. + * @return outer HTML. + * @see #outerHtml() + */ public String toString() { return outerHtml(); } @@ -632,7 +626,6 @@ public boolean equals(Object o) { * @param o other object to compare to * @return true if the content of this node is the same as the other */ - public boolean hasSameValue(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; diff --git a/src/main/java/org/jsoup/nodes/NodeUtils.java b/src/main/java/org/jsoup/nodes/NodeUtils.java new file mode 100644 index 0000000000..2a3983903b --- /dev/null +++ b/src/main/java/org/jsoup/nodes/NodeUtils.java @@ -0,0 +1,27 @@ +package org.jsoup.nodes; + +import org.jsoup.parser.HtmlTreeBuilder; +import org.jsoup.parser.Parser; + +/** + * Internal helpers for Nodes, to keep the actual node APIs relatively clean. A jsoup internal class, so don't use it as + * there is no contract API). + */ +final class NodeUtils { + /** + * Get the output setting for this node, or if this node has no document (or parent), retrieve the default output + * settings + */ + static Document.OutputSettings outputSettings(Node node) { + Document owner = node.ownerDocument(); + return owner != null ? owner.outputSettings() : (new Document("")).outputSettings(); + } + + /** + * Get the parser that was used to make this node, or the default HTML parser if it has no parent. + */ + static Parser parser(Node node) { + Document doc = node.ownerDocument(); + return doc != null && doc.parser() != null ? doc.parser() : new Parser(new HtmlTreeBuilder()); + } +} diff --git a/src/main/java/org/jsoup/nodes/PseudoTextElement.java b/src/main/java/org/jsoup/nodes/PseudoTextElement.java index cacec3f011..5d0cc1437f 100644 --- a/src/main/java/org/jsoup/nodes/PseudoTextElement.java +++ b/src/main/java/org/jsoup/nodes/PseudoTextElement.java @@ -2,8 +2,6 @@ import org.jsoup.parser.Tag; -import java.io.IOException; - /** * Represents a {@link TextNode} as an {@link Element}, to enable text nodes to be selected with * the {@link org.jsoup.select.Selector} {@code :matchText} syntax. @@ -15,10 +13,10 @@ public PseudoTextElement(Tag tag, String baseUri, Attributes attributes) { } @Override - void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) { } @Override - void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { } } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 455bd6c79f..23d3708308 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -556,10 +556,6 @@ List getPendingTableCharacters() { return pendingTableCharacters; } - void setPendingTableCharacters(List pendingTableCharacters) { - this.pendingTableCharacters = pendingTableCharacters; - } - /** 11.2.5.2 Closing elements that have implied end tags

When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index 919c9db35c..c544d18740 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -13,8 +13,6 @@ * in {@link org.jsoup.Jsoup}. */ public class Parser { - private static final int DEFAULT_MAX_ERRORS = 0; // by default, error tracking is disabled. - private TreeBuilder treeBuilder; private ParseErrorList errors; private ParseSettings settings; diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index d363bc2479..229ab36008 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -256,8 +256,7 @@ public Whitelist addAttributes(String tag, String... attributes) { Validate.isTrue(attributes.length > 0, "No attribute names supplied."); TagName tagName = TagName.valueOf(tag); - if (!tagNames.contains(tagName)) - tagNames.add(tagName); + tagNames.add(tagName); Set attributeSet = new HashSet<>(); for (String key : attributes) { Validate.notEmpty(key); @@ -335,8 +334,7 @@ public Whitelist addEnforcedAttribute(String tag, String attribute, String value Validate.notEmpty(value); TagName tagName = TagName.valueOf(tag); - if (!tagNames.contains(tagName)) - tagNames.add(tagName); + tagNames.add(tagName); AttributeKey attrKey = AttributeKey.valueOf(attribute); AttributeValue attrVal = AttributeValue.valueOf(value); @@ -632,9 +630,8 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; TypedValue other = (TypedValue) obj; if (value == null) { - if (other.value != null) return false; - } else if (!value.equals(other.value)) return false; - return true; + return other.value == null; + } else return value.equals(other.value); } @Override From 77966f79996f3fc4c7828b4d02a926c071faf040 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 12:42:22 -0700 Subject: [PATCH 260/774] Some redirect tests --- .../java/org/jsoup/helper/HttpConnection.java | 2 +- .../org/jsoup/integration/ConnectTest.java | 58 +++++++++++++++++-- .../integration/servlets/RedirectServlet.java | 35 +++++++++++ .../jsoup/integration/servlets/SlowRider.java | 4 +- 4 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/servlets/RedirectServlet.java diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index b363a98435..9f56a29196 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -740,7 +740,7 @@ else if (methodHasBody) } String location = res.header(LOCATION); - if (location != null && location.startsWith("http:/") && location.charAt(6) != '/') // fix broken Location: http:/temp/AAG_New/en/index.php + if (location.startsWith("http:/") && location.charAt(6) != '/') // fix broken Location: http:/temp/AAG_New/en/index.php location = location.substring(6); URL redir = StringUtil.resolve(req.url(), location); req.url(encodeUrl(redir)); diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index b6e1a1863a..dca0b55218 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -7,6 +7,7 @@ import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.HelloServlet; import org.jsoup.integration.servlets.InterruptedServlet; +import org.jsoup.integration.servlets.RedirectServlet; import org.jsoup.integration.servlets.SlowRider; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -25,6 +26,7 @@ import static org.jsoup.integration.UrlConnectTest.browserUa; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** @@ -34,13 +36,13 @@ public class ConnectTest { private static String echoUrl; @BeforeClass - public static void setUp() throws Exception { + public static void setUp() { TestServer.start(); echoUrl = EchoServlet.Url; } @AfterClass - public static void tearDown() throws Exception { + public static void tearDown() { TestServer.stop(); } @@ -80,7 +82,8 @@ public void exceptOnUnsupportedProtocol() { } private static String ihVal(String key, Document doc) { - return doc.select("th:contains(" + key + ") + td").first().text(); + final Element first = doc.select("th:contains(" + key + ") + td").first(); + return first != null ? first.text() : null; } @Test @@ -399,7 +402,7 @@ public void handlesEmptyStreamDuringParseRead() throws IOException { } catch (IOException e) { threw = true; } - assertEquals(true, threw); + assertTrue(threw); } @Test @@ -414,7 +417,52 @@ public void handlesEmtpyStreamDuringBufferdRead() throws IOException { } catch (UncheckedIOException e) { threw = true; } - assertEquals(true, threw); + assertTrue(threw); + } + @Test public void handlesRedirect() throws IOException { + Document doc = Jsoup.connect(RedirectServlet.Url) + .data(RedirectServlet.LocationParam, HelloServlet.Url) + .get(); + + Element p = doc.selectFirst("p"); + assertEquals("Hello, World!", p.text()); + + assertEquals(HelloServlet.Url, doc.location()); + } + + @Test public void handlesEmptyRedirect() throws IOException { + boolean threw = false; + try { + Connection.Response res = Jsoup.connect(RedirectServlet.Url) + .execute(); + } catch (IOException e) { + assertTrue(e.getMessage().contains("Too many redirects")); + threw = true; + } + assertTrue(threw); + } + + @Test public void doesNotPostFor302() throws IOException { + final Document doc = Jsoup.connect(RedirectServlet.Url) + .data("Hello", "there") + .data(RedirectServlet.LocationParam, EchoServlet.Url) + .post(); + + assertEquals(EchoServlet.Url, doc.location()); + assertEquals("GET", ihVal("Method", doc)); + assertNull(ihVal("Hello", doc)); // data not sent + } + + @Test public void doesPostFor307() throws IOException { + final Document doc = Jsoup.connect(RedirectServlet.Url) + .data("Hello", "there") + .data(RedirectServlet.LocationParam, EchoServlet.Url) + .data(RedirectServlet.CodeParam, "307") + .post(); + + assertEquals(EchoServlet.Url, doc.location()); + assertEquals("POST", ihVal("Method", doc)); + assertEquals("there", ihVal("Hello", doc)); } } diff --git a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java new file mode 100644 index 0000000000..8431347750 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java @@ -0,0 +1,35 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.integration.TestServer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class RedirectServlet extends BaseServlet { + public static final String Url = TestServer.map(RedirectServlet.class); + public static final String LocationParam = "loc"; + public static final String CodeParam = "code"; + private static final int DefaultCode = HttpServletResponse.SC_MOVED_TEMPORARILY; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { + String location = req.getParameter(LocationParam); + if (location == null) + location = ""; + + int intCode = DefaultCode; + String code = req.getParameter(CodeParam); + if (code != null) + intCode = Integer.parseInt(code); + + res.setHeader("Location", location); + res.setStatus(intCode); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doGet(req, res); + } +} diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java index 29f2a77091..5e5b550627 100644 --- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -2,7 +2,6 @@ import org.jsoup.integration.TestServer; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -16,9 +15,8 @@ public class SlowRider extends BaseServlet { private static final int SleepTime = 2000; public static final String MaxTimeParam = "maxTime"; - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { pause(1000); res.setContentType(TextHtml); res.setStatus(HttpServletResponse.SC_OK); From 6be19a6fa26e6e5e3d716283bad4b5de0348a8b7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 14:42:51 -0700 Subject: [PATCH 261/774] If a charset cannot encode, flip to one that can Fixes #1007 --- CHANGES | 3 +++ src/main/java/org/jsoup/helper/DataUtil.java | 7 ++++++- src/test/java/org/jsoup/parser/HtmlParserTest.java | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6f2e2aa344..ffa01417f9 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,9 @@ jsoup changelog HTML parser. Previously that only happened when the XML parser was specified. + * Improvement: if the document's input character does not support encoding, flip it to one that does. + + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 0e8e819481..f4012fc478 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -166,7 +166,12 @@ else if (first instanceof Comment) { // io exception when parsing (not seen before because reading the stream as we go) throw e.ioException(); } - doc.outputSettings().charset(charsetName); + Charset charset = Charset.forName(charsetName); + doc.outputSettings().charset(charset); + if (!charset.canEncode()) { + // some charsets can read but not encode; switch to an encodable charset and update the meta el + doc.charset(Charset.forName(defaultCharset)); + } } input.close(); return doc; diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index f583ed6182..108083bd76 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -18,6 +18,7 @@ import org.junit.Ignore; import org.junit.Test; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.List; @@ -1194,4 +1195,17 @@ public void testInvalidTableContents() throws IOException { assertEquals("A Certain Kind of Test", doc.head().select("title").text()); } + + @Test public void fallbackToUtfIfCantEncode() throws IOException { + // that charset can't be encoded, so make sure we flip to utf + + String in = "One"; + Document doc = Jsoup.parse(new ByteArrayInputStream(in.getBytes()), null, ""); + + assertEquals("UTF-8", doc.charset().name()); + assertEquals("One", doc.text()); + + String html = doc.outerHtml(); + assertEquals(" One", TextUtil.stripNewlines(html)); + } } From 39e90ee38c3ce7aa254bb89740282c12eb149162 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 15:06:19 -0700 Subject: [PATCH 262/774] Indentation fix --- .../java/org/jsoup/parser/HtmlParserTest.java | 156 +++++++++--------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 108083bd76..33766b053f 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -28,9 +28,10 @@ import static org.junit.Assert.assertTrue; /** - Tests for the Parser - - @author Jonathan Hedley, jonathan@hedley.net */ + * Tests for the Parser + * + * @author Jonathan Hedley, jonathan@hedley.net + */ public class HtmlParserTest { @Test public void parsesSimpleDocument() { @@ -59,7 +60,7 @@ public class HtmlParserTest { // this gets a

with attr '=a' and an OneSomething

\n" + - "Else", doc.body().html()); + "Else", doc.body().html()); doc = Jsoup.parse("

"); assertEquals("

", doc.body().html()); @@ -160,7 +161,6 @@ public class HtmlParserTest { String html = "foo bar baz"; Document doc = Jsoup.parse(html); assertEquals("foo bar baz", doc.text()); - } @Test public void handlesEscapedData() { @@ -271,13 +271,13 @@ public class HtmlParserTest { @Test public void noTableDirectInTable() { Document doc = Jsoup.parse(" * * - * + * * * *
One
Two
, must ignore or will close table assertEquals("
Three"); assertEquals("
One
Two
Three
", - TextUtil.stripNewlines(doc.body().html())); + TextUtil.stripNewlines(doc.body().html())); } @Test public void ignoresDupeEndTrTag() { Document doc = Jsoup.parse("
One
Two
Three
"); // two
One
Two
Three
", - TextUtil.stripNewlines(doc.body().html())); + TextUtil.stripNewlines(doc.body().html())); } @Test public void handlesBaseTags() { @@ -497,10 +497,10 @@ public class HtmlParserTest { String h = " "; Document doc = Jsoup.parse(h); assertEquals(" ", - TextUtil.stripNewlines(doc.html())); + TextUtil.stripNewlines(doc.html())); // no body auto vivification } - + @Test public void ignoresContentAfterFrameset() { String h = "One
"; Document doc = Jsoup.parse(h); @@ -529,7 +529,7 @@ public class HtmlParserTest { String h = "OneTwoThree FourFive Six Seven "; Document doc = Jsoup.parse(h); assertEquals(" OneTwoThreeFourFive Six Seven ", - TextUtil.stripNewlines(doc.html())); + TextUtil.stripNewlines(doc.html())); } @Test public void normalisesEmptyDocument() { @@ -540,13 +540,13 @@ public class HtmlParserTest { @Test public void normalisesHeadlessBody() { Document doc = Jsoup.parse("bar"); assertEquals(" bar", - TextUtil.stripNewlines(doc.html())); + TextUtil.stripNewlines(doc.html())); } @Test public void normalisedBodyAfterContent() { Document doc = Jsoup.parse("
One
"); assertEquals("
One
", - TextUtil.stripNewlines(doc.html())); + TextUtil.stripNewlines(doc.html())); } @Test public void findsCharsetInMalformedMeta() { @@ -634,22 +634,22 @@ public class HtmlParserTest { @Test public void handlesUnclosedFormattingElements() { // whatwg: formatting elements get collected and applied, but excess elements are thrown away String h = "\n" + - "

X\n" + - "

X\n" + - "

X\n" + - "

X"; + "

X\n" + + "

X\n" + + "

X\n" + + "

X"; Document doc = Jsoup.parse(h); doc.outputSettings().indentAmount(0); String want = "\n" + - "\n" + - " \n" + - "\n" + - "

X

\n" + - "

X

\n" + - "

X

\n" + - "

X

\n" + - "\n" + - ""; + "\n" + + " \n" + + "\n" + + "

X

\n" + + "

X

\n" + + "

X

\n" + + "

X

\n" + + "\n" + + ""; assertEquals(want, doc.html()); } @@ -673,14 +673,14 @@ public class HtmlParserTest { String h = "

One

Three

Four

Five

"; Document doc = Jsoup.parse(h); String want = "

One

\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "

Three

Four

Five

"; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "

Three

Four

Five

"; assertEquals(want, doc.body().html()); } @@ -792,17 +792,17 @@ public class HtmlParserTest { @Test public void handlesWhitespaceInoDocType() { String html = ""; + " PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\r\n" + + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; Document doc = Jsoup.parse(html); assertEquals("", doc.childNode(0).outerHtml()); } - + @Test public void tracksErrorsWhenRequested() { String html = "

One

&arrgh;
errors = parser.getErrors(); assertEquals(5, errors.size()); assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); @@ -832,7 +832,7 @@ public class HtmlParserTest { List errors = parser.getErrors(); assertEquals(0, errors.size()); } - + @Test public void handlesCommentsInTable() { String html = "
text
"; Document node = Jsoup.parseBodyFragment(html); @@ -841,16 +841,16 @@ public class HtmlParserTest { @Test public void handlesQuotesInCommentsInScripts() { String html = ""; + " \n" + + ""; Document node = Jsoup.parseBodyFragment(html); assertEquals("", node.body().html()); + " \n" + + "", node.body().html()); } @Test public void handleNullContextInParseFragment() { @@ -921,11 +921,11 @@ public class HtmlParserTest { @Test public void handlesInputInTable() { String h = "\n" + - "\n" + - "\n" + - "\n" + - "
\n" + - ""; + "\n" + + "\n" + + "\n" + + "
\n" + + ""; Document doc = Jsoup.parse(h); assertEquals(1, doc.select("table input").size()); assertEquals(2, doc.select("input").size()); @@ -942,31 +942,31 @@ public class HtmlParserTest { // would previously throw invalid name exception on empty doctype Document doc = Jsoup.parse(""); assertEquals( - " ", - StringUtil.normaliseWhitespace(doc.outerHtml())); + " ", + StringUtil.normaliseWhitespace(doc.outerHtml())); doc = Jsoup.parse("

Foo

"); assertEquals( - "

Foo

", - StringUtil.normaliseWhitespace(doc.outerHtml())); + "

Foo

", + StringUtil.normaliseWhitespace(doc.outerHtml())); doc = Jsoup.parse(""); assertEquals( - " ", - StringUtil.normaliseWhitespace(doc.outerHtml())); + " ", + StringUtil.normaliseWhitespace(doc.outerHtml())); } - + @Test public void handlesManyChildren() { // Arrange StringBuilder longBody = new StringBuilder(500000); for (int i = 0; i < 25000; i++) { longBody.append(i).append("
"); } - + // Act long start = System.currentTimeMillis(); Document doc = Parser.parseBodyFragment(longBody.toString(), ""); - + // Assert assertEquals(50000, doc.body().childNodeSize()); assertTrue(System.currentTimeMillis() - start < 1000); @@ -1012,7 +1012,7 @@ public void testInvalidTableContents() throws IOException { Document doc = Jsoup.parse(""); String html = doc.outerHtml(); assertEquals("


", - StringUtil.normaliseWhitespace(doc.body().html())); + StringUtil.normaliseWhitespace(doc.body().html())); } @Test public void testReinsertionModeForThCelss() { @@ -1150,7 +1150,7 @@ public void testInvalidTableContents() throws IOException { assertEquals("

test

Two
", StringUtil.normaliseWhitespace(clean)); } - @Test public void testTemplateInsideTable() throws IOException { + @Test public void testTemplateInsideTable() throws IOException { File in = ParseTest.getFile("/htmltests/table-polymer-template.html"); Document doc = Jsoup.parse(in, "UTF-8"); doc.outputSettings().prettyPrint(true); @@ -1159,9 +1159,9 @@ public void testInvalidTableContents() throws IOException { for (Element template : templates) { assertTrue(template.childNodes().size() > 1); } - } + } - @Test public void testHandlesDeepSpans() { + @Test public void testHandlesDeepSpans() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 200; i++) { sb.append(""); @@ -1172,29 +1172,29 @@ public void testInvalidTableContents() throws IOException { Document doc = Jsoup.parse(sb.toString()); assertEquals(200, doc.select("span").size()); assertEquals(1, doc.select("p").size()); - } + } - @Test public void commentAtEnd() throws Exception { - Document doc = Jsoup.parse("\n\nOne\nTwo\n"); Element pre = doc.selectFirst("pre"); assertEquals("One\nTwo", pre.text()); assertEquals("\nOne\nTwo\n", pre.wholeText()); - } + } - @Test public void handlesXmlDeclAndCommentsBeforeDoctype() throws IOException { - File in = ParseTest.getFile("/htmltests/comments.html"); - Document doc = Jsoup.parse(in, "UTF-8"); + @Test public void handlesXmlDeclAndCommentsBeforeDoctype() throws IOException { + File in = ParseTest.getFile("/htmltests/comments.html"); + Document doc = Jsoup.parse(in, "UTF-8"); - assertEquals(" A Certain Kind of Test

Hello

h1> (There is a UTF8 hidden BOM at the top of this file.) ", - StringUtil.normaliseWhitespace(doc.html())); + assertEquals(" A Certain Kind of Test

Hello

h1> (There is a UTF8 hidden BOM at the top of this file.) ", + StringUtil.normaliseWhitespace(doc.html())); - assertEquals("A Certain Kind of Test", doc.head().select("title").text()); - } + assertEquals("A Certain Kind of Test", doc.head().select("title").text()); + } @Test public void fallbackToUtfIfCantEncode() throws IOException { // that charset can't be encoded, so make sure we flip to utf From bdf1df7eb3ca76cdcdaca38f7df5d941bbb1c664 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 15:25:23 -0700 Subject: [PATCH 263/774] Treat < in a start tag as a new tag, not an attribute name Fixes #797 --- CHANGES | 5 +++++ src/main/java/org/jsoup/parser/CharacterReader.java | 3 ++- src/main/java/org/jsoup/parser/TokeniserState.java | 9 ++++++++- src/test/java/org/jsoup/parser/HtmlParserTest.java | 9 ++++++--- .../java/org/jsoup/parser/TokeniserStateTest.java | 13 +++++++++++-- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index ffa01417f9..4c57038ee5 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,11 @@ jsoup changelog * Improvement: if the document's input character does not support encoding, flip it to one that does. + * Improvement: if a start tag is missing a > and a new tag is seen with a <, treat that as a new tag. (This differs + from the HTML5 spec, which would make at attribute with a name beginning with <, but in practice this impacts too + many pages. + + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index b3b82e9cf4..69935244c4 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -243,6 +243,7 @@ String consumeData() { String consumeTagName() { // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar + // NOTE: out of spec, added '<' to fix common author bugs bufferUp(); final int start = bufPos; final int remaining = bufLength; @@ -250,7 +251,7 @@ String consumeTagName() { while (bufPos < remaining) { final char c = val[bufPos]; - if (c == '\t'|| c == '\n'|| c == '\r'|| c == '\f'|| c == ' '|| c == '/'|| c == '>'|| c == TokeniserState.nullChar) + if (c == '\t'|| c == '\n'|| c == '\r'|| c == '\f'|| c == ' '|| c == '/'|| c == '>'|| c == '<' || c == TokeniserState.nullChar) break; bufPos++; } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 7d404259e3..95b326cc77 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -158,6 +158,10 @@ void read(Tokeniser t, CharacterReader r) { case '/': t.transition(SelfClosingStartTag); break; + case '<': // NOTE: out of spec, but clear author intent + t.error(this); + r.unconsume(); + // intended fall through to next > case '>': t.emitTagPending(); t.transition(Data); @@ -560,6 +564,10 @@ void read(Tokeniser t, CharacterReader r) { case '/': t.transition(SelfClosingStartTag); break; + case '<': // NOTE: out of spec, but clear (spec has this as a part of the attribute name) + t.error(this); + r.unconsume(); + // intended fall through as if > case '>': t.emitTagPending(); t.transition(Data); @@ -576,7 +584,6 @@ void read(Tokeniser t, CharacterReader r) { break; case '"': case '\'': - case '<': case '=': t.error(this); t.tagPending.newAttribute(); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 33766b053f..e7d689e60f 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -57,10 +57,13 @@ public class HtmlParserTest { @Test public void parsesQuiteRoughAttributes() { String html = "

OneSomething

Else"; - // this gets a

with attr '=a' and an with attr '=a' and an OneSomething

\n" + - "Else", doc.body().html()); + + // NOTE: per spec this should be the test case. but impacts too many ppl + // assertEquals("

OneSomething

\nElse", doc.body().html()); + + assertEquals("

One

Something

Else", TextUtil.stripNewlines(doc.body().html())); doc = Jsoup.parse("

"); assertEquals("

", doc.body().html()); diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index 28bd552a4f..b50965eecf 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -1,8 +1,7 @@ package org.jsoup.parser; -import static org.junit.Assert.*; - import org.jsoup.Jsoup; +import org.jsoup.TextUtil; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -12,6 +11,9 @@ import java.util.Arrays; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + public class TokeniserStateTest { final char[] whiteSpace = { '\t', '\n', '\r', '\f', ' ' }; @@ -197,4 +199,11 @@ public void testPublicAndSystemIdentifiersWithWhitespace() { } } } + + @Test public void handlesLessInTagThanAsNewTag() { + // out of spec, but clear author intent + String html = "Two"; + Document doc = Jsoup.parse(html); + assertEquals("

Two
", TextUtil.stripNewlines(doc.body().html())); + } } From a810d2e3615da9a37ad74a7db2ca8bc6945ab9a8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 29 Apr 2018 16:16:12 -0700 Subject: [PATCH 264/774] Don't blow up if a namespace is undefined Fixes #848 --- CHANGES | 4 ++++ src/main/java/org/jsoup/helper/W3CDom.java | 5 ++++- .../java/org/jsoup/helper/W3CDomTest.java | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4c57038ee5..edf0e86cc9 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,10 @@ jsoup changelog many pages. + * Bugfix: when converting a Jsoup document to a W3C DOM, if an element is namespaced but not in a defined namespace, + set it to the global namespace. + + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 81ac932499..68475747b1 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -86,8 +86,11 @@ public void head(org.jsoup.nodes.Node source, int depth) { String prefix = updateNamespaces(sourceEl); String namespace = namespacesStack.peek().get(prefix); + String tagName = sourceEl.tagName(); - Element el = doc.createElementNS(namespace, sourceEl.tagName()); + Element el = namespace == null && tagName.contains(":") ? + doc.createElementNS("", tagName) : // doesn't have a real namespace defined + doc.createElementNS(namespace, tagName); copyAttributes(sourceEl, el); if (dest == null) { // sets up the root doc.appendChild(el); diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index f4d46d89ea..e209321ee8 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -12,6 +12,7 @@ import java.io.IOException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class W3CDomTest { @@ -135,5 +136,23 @@ public void handlesInvalidAttributeNames() { Document w3Doc = new W3CDom().fromJsoup(jsoupDoc); } + + @Test public void treatsUndeclaredNamespaceAsLocalName() { + String html = "One"; + org.jsoup.nodes.Document doc = Jsoup.parse(html); + + Document w3Doc = new W3CDom().fromJsoup(doc); + Node htmlEl = w3Doc.getFirstChild(); + + assertNull(htmlEl.getNamespaceURI()); + assertEquals("html", htmlEl.getLocalName()); + assertEquals("html", htmlEl.getNodeName()); + + Node fb = htmlEl.getFirstChild().getNextSibling().getFirstChild(); + assertNull(fb.getNamespaceURI()); + assertEquals("like", fb.getLocalName()); + assertEquals("fb:like", fb.getNodeName()); + + } } From 20ec85e678bb20d5988491d50789811ed1ee9eca Mon Sep 17 00:00:00 2001 From: pipiZ Date: Wed, 2 May 2018 21:11:25 +0800 Subject: [PATCH 265/774] Add methods to get all the element siblings before or after current element --- src/main/java/org/jsoup/nodes/Element.java | 40 +++++++++++++++++++ .../java/org/jsoup/nodes/ElementTest.java | 29 ++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index a966b600b5..61420bff8d 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -687,6 +687,26 @@ public Element nextElementSibling() { return null; } + /** + * Get all the siblings after this element. + * + * @return all the siblings after this element, or null if there is no siblings after this + */ + public List nextElementSiblings() { + if (parentNode == null) { + return null; + } + + List siblings = parent().childElementsList(); + int index = indexInList(this, siblings); + Validate.notNull(index); + + if (siblings.size() > index + 1) { + return siblings.subList(index + 1, siblings.size()); + } + return null; + } + /** * Gets the previous element sibling of this element. * @return the previous element, or null if there is no previous element @@ -703,6 +723,26 @@ public Element previousElementSibling() { return null; } + /** + * Get all the element siblings before this element. + * + * @return all the previous element siblings, or null if no previous siblings + */ + public List previousElementSiblings() { + if (parentNode == null) { + return null; + } + + List siblings = parent().childElementsList(); + int index = indexInList(this, siblings); + Validate.notNull(index); + + if (index > 0 && index < siblings.size()) { + return siblings.subList(0, index); + } + return null; + } + /** * Gets the first element sibling of this element. * @return the first sibling that is an element (aka the parent's first element child) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 70a58f06a3..9e591815bb 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1324,4 +1324,33 @@ public void textHasSpaceAfterBlockTags() { assertEquals("One Two", doc.text()); } + @Test + public void testNextElementSiblings() { + Document doc = Jsoup.parse("
  • a
  • " + + "
  • b
  • " + + "
  • c
  • "); + Element element = doc.getElementById("a"); + List elementSiblings = element.nextElementSiblings(); + assertNotNull(elementSiblings); + assertEquals(2, elementSiblings.size()); + + Element element1 = doc.getElementById("c"); + List elementSiblings1 = element1.nextElementSiblings(); + assertNull(elementSiblings1); + } + + @Test + public void testPreviousElementSiblings() { + Document doc = Jsoup.parse("
  • a
  • " + + "
  • b
  • " + + "
  • c
  • "); + Element element = doc.getElementById("b"); + List elementSiblings = element.previousElementSiblings(); + assertNotNull(elementSiblings); + assertEquals(1, elementSiblings.size()); + + Element element1 = doc.getElementById("a"); + List elementSiblings1 = element1.previousElementSiblings(); + assertNull(elementSiblings1); + } } From 59f10777132745ec84eefad77dcabd2109947e97 Mon Sep 17 00:00:00 2001 From: pipiZ Date: Fri, 4 May 2018 20:10:34 +0800 Subject: [PATCH 266/774] Element#previousElementSiblings() and Element#nextElementSiblings now return empty list --- src/main/java/org/jsoup/nodes/Element.java | 12 ++-- .../java/org/jsoup/nodes/ElementTest.java | 71 +++++++++++++++---- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 61420bff8d..dc8ec1c419 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -690,11 +690,11 @@ public Element nextElementSibling() { /** * Get all the siblings after this element. * - * @return all the siblings after this element, or null if there is no siblings after this + * @return all the siblings after this element, or empty list if there is no siblings after this */ public List nextElementSiblings() { if (parentNode == null) { - return null; + return Collections.emptyList(); } List siblings = parent().childElementsList(); @@ -704,7 +704,7 @@ public List nextElementSiblings() { if (siblings.size() > index + 1) { return siblings.subList(index + 1, siblings.size()); } - return null; + return Collections.emptyList(); } /** @@ -726,11 +726,11 @@ public Element previousElementSibling() { /** * Get all the element siblings before this element. * - * @return all the previous element siblings, or null if no previous siblings + * @return all the previous element siblings, or empty list if no previous siblings */ public List previousElementSiblings() { if (parentNode == null) { - return null; + return Collections.emptyList(); } List siblings = parent().childElementsList(); @@ -740,7 +740,7 @@ public List previousElementSiblings() { if (index > 0 && index < siblings.size()) { return siblings.subList(0, index); } - return null; + return Collections.emptyList(); } /** diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 9e591815bb..c985eaa105 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -14,12 +14,7 @@ import java.util.Map; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * Tests for Element (DOM stuff mostly). @@ -1326,31 +1321,81 @@ public void textHasSpaceAfterBlockTags() { @Test public void testNextElementSiblings() { - Document doc = Jsoup.parse("
  • a
  • " + + Document doc = Jsoup.parse("
      " + + "
    • a
    • " + "
    • b
    • " + - "
    • c
    • "); + "
    • c
    • " + + "
    " + + "
    " + + "
  • d
  • " + + "
    "); + Element element = doc.getElementById("a"); List elementSiblings = element.nextElementSiblings(); assertNotNull(elementSiblings); assertEquals(2, elementSiblings.size()); + assertEquals("b", elementSiblings.get(0).id()); + assertEquals("c", elementSiblings.get(1).id()); - Element element1 = doc.getElementById("c"); + Element element1 = doc.getElementById("b"); List elementSiblings1 = element1.nextElementSiblings(); - assertNull(elementSiblings1); + assertNotNull(elementSiblings1); + assertEquals(1, elementSiblings1.size()); + assertEquals("c", elementSiblings1.get(0).id()); + + Element element2 = doc.getElementById("c"); + List elementSiblings2 = element2.nextElementSiblings(); + assertEquals(0, elementSiblings2.size()); + + Element ul = doc.getElementById("ul"); + List elementSiblings3 = ul.nextElementSiblings(); + assertNotNull(elementSiblings3); + assertEquals(1, elementSiblings3.size()); + assertEquals("div", elementSiblings3.get(0).id()); + + Element div = doc.getElementById("div"); + List elementSiblings4 = div.nextElementSiblings(); + try { + Element elementSibling = elementSiblings4.get(0); + fail("This element should has no next siblings"); + } catch (IndexOutOfBoundsException e) { + } } @Test public void testPreviousElementSiblings() { - Document doc = Jsoup.parse("
  • a
  • " + + Document doc = Jsoup.parse("
      " + + "
    • a
    • " + "
    • b
    • " + - "
    • c
    • "); + "
    • c
    • " + + "
    " + + "
    " + + "
  • d
  • " + + "
    "); + Element element = doc.getElementById("b"); List elementSiblings = element.previousElementSiblings(); assertNotNull(elementSiblings); assertEquals(1, elementSiblings.size()); + assertEquals("a", elementSiblings.get(0).id()); Element element1 = doc.getElementById("a"); List elementSiblings1 = element1.previousElementSiblings(); - assertNull(elementSiblings1); + assertEquals(0, elementSiblings1.size()); + + Element element2 = doc.getElementById("c"); + List elementSiblings2 = element2.previousElementSiblings(); + assertNotNull(elementSiblings2); + assertEquals(2, elementSiblings2.size()); + assertEquals("a", elementSiblings2.get(0).id()); + assertEquals("b", elementSiblings2.get(1).id()); + + Element ul = doc.getElementById("ul"); + List elementSiblings3 = ul.previousElementSiblings(); + try { + Element element3 = elementSiblings3.get(0); + fail("This element should has no previous siblings"); + } catch (IndexOutOfBoundsException e) { + } } } From eec4cf3503d226272642c7507686e96870307dfb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 5 May 2018 23:20:29 -0700 Subject: [PATCH 267/774] Call .disconnect() in close, so Android OKHttp impl can clean its pool --- src/main/java/org/jsoup/helper/HttpConnection.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 9f56a29196..3c6c5cc7ce 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -671,6 +671,7 @@ public static class Response extends HttpConnection.Base im private String statusMessage; private ByteBuffer byteData; private InputStream bodyStream; + private HttpURLConnection conn; private String charset; private String contentType; private boolean executed = false; @@ -910,6 +911,10 @@ private static HttpURLConnection createConnection(Connection.Request req) throws * Call on completion of stream read, to close the body (or error) stream */ private void safeClose() { + if (conn != null) { + conn.disconnect(); + conn = null; + } if (bodyStream != null) { try { bodyStream.close(); @@ -922,7 +927,8 @@ private void safeClose() { } // set up url, method, header, cookies - private void setupFromConnection(HttpURLConnection conn, Connection.Response previousResponse) throws IOException { + private void setupFromConnection(HttpURLConnection conn, HttpConnection.Response previousResponse) throws IOException { + this.conn = conn; method = Method.valueOf(conn.getRequestMethod()); url = conn.getURL(); statusCode = conn.getResponseCode(); @@ -938,6 +944,7 @@ private void setupFromConnection(HttpURLConnection conn, Connection.Response pre if (!hasCookie(prevCookie.getKey())) cookie(prevCookie.getKey(), prevCookie.getValue()); } + previousResponse.safeClose(); } } From ae99d7ebaa7ce4e8446150ce4ccbad4646433f8f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 6 May 2018 11:24:09 -0700 Subject: [PATCH 268/774] Table parse performance tweak --- CHANGES | 2 + .../org/jsoup/parser/CharacterReader.java | 42 +++++++++++-------- .../jsoup/parser/HtmlTreeBuilderState.java | 13 +++--- .../parser/HtmlTreeBuilderStateTest.java | 6 ++- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index edf0e86cc9..bb4d7da4d0 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,8 @@ jsoup changelog many pages. + * Improvement: performance optimization when parsing tables. + * Bugfix: when converting a Jsoup document to a W3C DOM, if an element is namespaced but not in a defined namespace, set it to the global namespace. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 69935244c4..9253bf618f 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -44,17 +44,18 @@ public CharacterReader(String input) { } private void bufferUp() { - if (bufPos < bufSplitPoint) + final int pos = bufPos; + if (pos < bufSplitPoint) return; try { - reader.skip(bufPos); + reader.skip(pos); reader.mark(maxBufferLen); final int read = reader.read(charBuf); reader.reset(); if (read != -1) { bufLength = read; - readerPos += bufPos; + readerPos += pos; bufPos = 0; bufMark = 0; bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; @@ -197,16 +198,20 @@ public String consumeToAny(final char... chars) { final int start = bufPos; final int remaining = bufLength; final char[] val = charBuf; + final int charLen = chars.length; + int pos = bufPos; + int i; - OUTER: while (bufPos < remaining) { - for (char c : chars) { - if (val[bufPos] == c) + OUTER: while (pos < remaining) { + for (i = 0; i < charLen; i++) { + if (val[pos] == chars[i]) break OUTER; } - bufPos++; + pos++; } - return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; + bufPos = pos; + return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeToAnySorted(final char... chars) { @@ -214,14 +219,15 @@ String consumeToAnySorted(final char... chars) { final int start = bufPos; final int remaining = bufLength; final char[] val = charBuf; + int pos = bufPos; - while (bufPos < remaining) { - if (Arrays.binarySearch(chars, val[bufPos]) >= 0) + while (pos < remaining) { + if (Arrays.binarySearch(chars, val[pos]) >= 0) break; - bufPos++; + pos++; } - - return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; + bufPos = pos; + return bufPos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeData() { @@ -248,15 +254,17 @@ String consumeTagName() { final int start = bufPos; final int remaining = bufLength; final char[] val = charBuf; + int pos = bufPos; - while (bufPos < remaining) { - final char c = val[bufPos]; + while (pos < remaining) { + final char c = val[pos]; if (c == '\t'|| c == '\n'|| c == '\r'|| c == '\f'|| c == ' '|| c == '/'|| c == '>'|| c == '<' || c == TokeniserState.nullChar) break; - bufPos++; + pos++; } - return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; + bufPos = pos; + return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeToEnd() { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 26d7d7ac14..8b92cdbd8e 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1163,7 +1163,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { Token.EndTag endTag = t.asEndTag(); String name = endTag.normalName(); - if (StringUtil.in(name, "td", "th")) { + if (StringUtil.inSorted(name, Constants.InCellNames)) { if (!tb.inTableScope(name)) { tb.error(this); tb.transition(InRow); // might not be in scope if empty:
    and processing fake end tag @@ -1175,10 +1175,10 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); tb.transition(InRow); - } else if (StringUtil.in(name, "body", "caption", "col", "colgroup", "html")) { + } else if (StringUtil.inSorted(name, Constants.InCellBody)) { tb.error(this); return false; - } else if (StringUtil.in(name, "table", "tbody", "tfoot", "thead", "tr")) { + } else if (StringUtil.inSorted(name, Constants.InCellTable)) { if (!tb.inTableScope(name)) { tb.error(this); return false; @@ -1189,8 +1189,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return anythingElse(t, tb); } } else if (t.isStartTag() && - StringUtil.in(t.asStartTag().normalName(), - "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr")) { + StringUtil.inSorted(t.asStartTag().normalName(), Constants.InCellCol)) { if (!(tb.inTableScope("td") || tb.inTableScope("th"))) { tb.error(this); return false; @@ -1521,5 +1520,9 @@ static final class Constants { "nav", "ol", "pre", "section", "summary", "ul"}; static final String[] InBodyEndAdoptionFormatters = new String[]{"a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u"}; static final String[] InBodyEndTableFosters = new String[]{"table", "tbody", "tfoot", "thead", "tr"}; + static final String[] InCellNames = new String[]{"td", "th"}; + static final String[] InCellBody = new String[]{"body", "caption", "col", "colgroup", "html"}; + static final String[] InCellTable = new String[]{ "table", "tbody", "tfoot", "thead", "tr"}; + static final String[] InCellCol = new String[]{"caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"}; } } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 80ca688afe..953dd14e42 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -27,7 +27,11 @@ public void ensureArraysAreSorted() { Constants.InBodyStartDrop, Constants.InBodyEndClosers, Constants.InBodyEndAdoptionFormatters, - Constants.InBodyEndTableFosters + Constants.InBodyEndTableFosters, + Constants.InCellNames, + Constants.InCellBody, + Constants.InCellTable, + Constants.InCellCol, }; for (String[] array : arrays) { From 16491229eeee7cea72d35ff0de6ed7302fd088de Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 6 May 2018 12:39:02 -0700 Subject: [PATCH 269/774] Perf tweaks in start tags, data --- CHANGES | 2 +- .../org/jsoup/parser/CharacterReader.java | 52 ++++++++++++------- .../org/jsoup/parser/HtmlTreeBuilder.java | 8 +-- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/CHANGES b/CHANGES index bb4d7da4d0..e6afd38c54 100644 --- a/CHANGES +++ b/CHANGES @@ -22,7 +22,7 @@ jsoup changelog many pages. - * Improvement: performance optimization when parsing tables. + * Improvement: performance tweaks when parsing start tags, data, tables. * Bugfix: when converting a Jsoup document to a W3C DOM, if an element is namespaced but not in a defined namespace, set it to the global namespace. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 9253bf618f..1bc6092944 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -195,11 +195,11 @@ String consumeTo(String seq) { */ public String consumeToAny(final char... chars) { bufferUp(); - final int start = bufPos; + int pos = bufPos; + final int start = pos; final int remaining = bufLength; final char[] val = charBuf; final int charLen = chars.length; - int pos = bufPos; int i; OUTER: while (pos < remaining) { @@ -216,10 +216,10 @@ public String consumeToAny(final char... chars) { String consumeToAnySorted(final char... chars) { bufferUp(); - final int start = bufPos; + int pos = bufPos; + final int start = pos; final int remaining = bufLength; final char[] val = charBuf; - int pos = bufPos; while (pos < remaining) { if (Arrays.binarySearch(chars, val[pos]) >= 0) @@ -232,34 +232,48 @@ String consumeToAnySorted(final char... chars) { String consumeData() { // &, <, null - bufferUp(); - final int start = bufPos; + //bufferUp(); // no need to bufferUp, just called consume() + int pos = bufPos; + final int start = pos; final int remaining = bufLength; final char[] val = charBuf; - while (bufPos < remaining) { - final char c = val[bufPos]; - if (c == '&'|| c == '<' || c == TokeniserState.nullChar) - break; - bufPos++; + OUTER: while (pos < remaining) { + switch (val[pos]) { + case '&': + case '<': + case TokeniserState.nullChar: + break OUTER; + default: + pos++; + } } - - return bufPos > start ? cacheString(charBuf, stringCache, start, bufPos -start) : ""; + bufPos = pos; + return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeTagName() { // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar // NOTE: out of spec, added '<' to fix common author bugs bufferUp(); - final int start = bufPos; + int pos = bufPos; + final int start = pos; final int remaining = bufLength; final char[] val = charBuf; - int pos = bufPos; - while (pos < remaining) { - final char c = val[pos]; - if (c == '\t'|| c == '\n'|| c == '\r'|| c == '\f'|| c == ' '|| c == '/'|| c == '>'|| c == '<' || c == TokeniserState.nullChar) - break; + OUTER: while (pos < remaining) { + switch (val[pos]) { + case '\t': + case '\n': + case '\r': + case '\f': + case ' ': + case '/': + case '>': + case '<': + case TokeniserState.nullChar: + break OUTER; + } pos++; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 23d3708308..9e99d712e0 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -252,9 +252,9 @@ void insert(Token.Comment commentToken) { } void insert(Token.Character characterToken) { - Node node; - // characters in script and style go in as datanodes, not text nodes - final String tagName = currentElement().tagName(); + final Node node; + final Element el = currentElement(); + final String tagName = el.tagName(); final String data = characterToken.getData(); if (characterToken.isCData()) @@ -263,7 +263,7 @@ else if (tagName.equals("script") || tagName.equals("style")) node = new DataNode(data); else node = new TextNode(data); - currentElement().appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack. + el.appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack. } private void insertNode(Node node) { From c8c05694da5489221c27e45a5e49d0f5fcb41863 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Wed, 9 May 2018 22:18:19 -0700 Subject: [PATCH 270/774] Use a stack for StringBuilder flyweight, so threads can use more than one at a time --- src/main/java/org/jsoup/helper/DataUtil.java | 4 +- .../java/org/jsoup/helper/HttpConnection.java | 8 +-- .../java/org/jsoup/helper/StringUtil.java | 64 ++++++++++++------- src/main/java/org/jsoup/nodes/Attribute.java | 7 +- src/main/java/org/jsoup/nodes/Attributes.java | 7 +- src/main/java/org/jsoup/nodes/Element.java | 23 ++++--- src/main/java/org/jsoup/nodes/Entities.java | 4 +- src/main/java/org/jsoup/nodes/Node.java | 4 +- .../java/org/jsoup/nodes/XmlDeclaration.java | 5 +- .../java/org/jsoup/parser/TokenQueue.java | 4 +- src/main/java/org/jsoup/parser/Tokeniser.java | 4 +- src/main/java/org/jsoup/select/Elements.java | 13 ++-- .../java/org/jsoup/select/QueryParser.java | 4 +- 13 files changed, 89 insertions(+), 62 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index f4012fc478..d09034da76 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -229,12 +229,12 @@ private static String validateCharset(String cs) { * Creates a random string, suitable for use as a mime boundary */ static String mimeBoundary() { - final StringBuilder mime = new StringBuilder(boundaryLength); + final StringBuilder mime = StringUtil.borrowBuilder(); final Random rand = new Random(); for (int i = 0; i < boundaryLength; i++) { mime.append(mimeBoundaryChars[rand.nextInt(mimeBoundaryChars.length)]); } - return mime.toString(); + return StringUtil.releaseBuilder(mime); } private static BomCharset detectCharsetFromBom(final ByteBuffer byteData) { diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 3c6c5cc7ce..7469192538 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -1066,7 +1066,7 @@ private static void writePost(final Connection.Request req, final OutputStream o } private static String getRequestCookieString(Connection.Request req) { - StringBuilder sb = StringUtil.stringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); boolean first = true; for (Map.Entry cookie : req.cookies().entrySet()) { if (!first) @@ -1076,13 +1076,13 @@ private static String getRequestCookieString(Connection.Request req) { sb.append(cookie.getKey()).append('=').append(cookie.getValue()); // todo: spec says only ascii, no escaping / encoding defined. validate on set? or escape somehow here? } - return sb.toString(); + return StringUtil.releaseBuilder(sb); } // for get url reqs, serialise the data map into the url private static void serialiseRequestUrl(Connection.Request req) throws IOException { URL in = req.url(); - StringBuilder url = StringUtil.stringBuilder(); + StringBuilder url = StringUtil.borrowBuilder(); boolean first = true; // reconstitute the query, ready for appends url @@ -1106,7 +1106,7 @@ private static void serialiseRequestUrl(Connection.Request req) throws IOExcepti .append('=') .append(URLEncoder.encode(keyVal.value(), DataUtil.defaultCharset)); } - req.url(new URL(url.toString())); + req.url(new URL(StringUtil.releaseBuilder(url))); req.data().clear(); // moved into url as get params } } diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/helper/StringUtil.java index 71aa7feb54..c30aa488f7 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/helper/StringUtil.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.Stack; /** * A minimal String utility class. Designed for internal jsoup use only. @@ -39,12 +40,12 @@ public static String join(Iterator strings, String sep) { if (!strings.hasNext()) // only one, avoid builder return start; - StringBuilder sb = new StringBuilder(64).append(start); + StringBuilder sb = StringUtil.borrowBuilder().append(start); while (strings.hasNext()) { sb.append(sep); sb.append(strings.next()); } - return sb.toString(); + return StringUtil.releaseBuilder(sb); } /** @@ -140,9 +141,9 @@ public static boolean isInvisibleChar(int c) { * @return normalised string */ public static String normaliseWhitespace(String string) { - StringBuilder sb = StringUtil.stringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); appendNormalisedWhitespace(sb, string, false); - return sb.toString(); + return StringUtil.releaseBuilder(sb); } /** @@ -226,30 +227,49 @@ public static String resolve(final String baseUrl, final String relUrl) { } } + private static final Stack builders = new Stack<>(); + /** - * Maintains a cached StringBuilder, to minimize new StringBuilder GCs. Prevents it from growing too big per thread. - * Care must be taken to not grab more than one in the same stack (not locked or mutexed or anything). + * Maintains cached StringBuilders in a flyweight pattern, to minimize new StringBuilder GCs. The StringBuilder is + * prevented from growing too large. + *

    + * Care must be taken to release the builder once its work has been completed, with {@see #releaseBuilder} * @return an empty StringBuilder + * @ */ - public static StringBuilder stringBuilder() { - StringBuilder sb = stringLocal.get(); - if (sb.length() > MaxCachedBuilderSize) { - sb = new StringBuilder(MaxCachedBuilderSize); - stringLocal.set(sb); - } else { - sb.delete(0, sb.length()); + public static StringBuilder borrowBuilder() { + synchronized (builders) { + return builders.empty() ? + new StringBuilder(MaxCachedBuilderSize) : + builders.pop(); } - return sb; - } - private static final int MaxCachedBuilderSize = 8 * 1024; - private static final ThreadLocal stringLocal = new ThreadLocal(){ - @Override - protected StringBuilder initialValue() { - return new StringBuilder(MaxCachedBuilderSize); - } - }; + /** + * Release a borrowed builder. Care must be taken not to use the builder after it has been returned, as its + * contents may be changed by this method, or by a concurrent thread. + * @param sb the StringBuilder to release. + * @return the string value of the released String Builder (as an incentive to release it!). + */ + public static String releaseBuilder(StringBuilder sb) { + Validate.notNull(sb); + String string = sb.toString(); + if (sb.length() > MaxCachedBuilderSize) + sb = new StringBuilder(MaxCachedBuilderSize); // make sure it hasn't grown too big + else + sb.delete(0, sb.length()); // make sure it's emptied on release + synchronized (builders) { + builders.push(sb); + + while (builders.size() > MaxIdleBuilders) { + builders.pop(); + } + } + return string; + } + + private static final int MaxCachedBuilderSize = 8 * 1024; + private static final int MaxIdleBuilders = 8; } diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 698755c208..bca9fe608f 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; +import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; @@ -98,14 +99,14 @@ public String setValue(String val) { @return HTML */ public String html() { - StringBuilder accum = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); try { - html(accum, (new Document("")).outputSettings()); + html(sb, (new Document("")).outputSettings()); } catch(IOException exception) { throw new SerializationException(exception); } - return accum.toString(); + return StringUtil.releaseBuilder(sb); } protected static void html(String key, String val, Appendable accum, Document.OutputSettings out) throws IOException { diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 1f74bf32b1..c4e373ce7a 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; +import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; @@ -298,13 +299,13 @@ public Map dataset() { @throws SerializationException if the HTML representation of the attributes cannot be constructed. */ public String html() { - StringBuilder accum = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); try { - html(accum, (new Document("")).outputSettings()); // output settings a bit funky, but this html() seldom used + html(sb, (new Document("")).outputSettings()); // output settings a bit funky, but this html() seldom used } catch (IOException e) { // ought never happen throw new SerializationException(e); } - return accum.toString(); + return StringUtil.releaseBuilder(sb); } final void html(final Appendable accum, final Document.OutputSettings out) throws IOException { diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index a966b600b5..36a73dfd79 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1020,7 +1020,7 @@ public Elements getAllElements() { * @see #textNodes() */ public String text() { - final StringBuilder accum = new StringBuilder(); + final StringBuilder accum = StringUtil.borrowBuilder(); NodeTraversor.traverse(new NodeVisitor() { public void head(Node node, int depth) { if (node instanceof TextNode) { @@ -1045,7 +1045,8 @@ public void tail(Node node, int depth) { } }, this); - return accum.toString().trim(); + + return StringUtil.releaseBuilder(accum).trim(); } /** @@ -1056,7 +1057,7 @@ public void tail(Node node, int depth) { * @see #text() */ public String wholeText() { - final StringBuilder accum = new StringBuilder(); + final StringBuilder accum = StringUtil.borrowBuilder(); NodeTraversor.traverse(new NodeVisitor() { public void head(Node node, int depth) { if (node instanceof TextNode) { @@ -1068,7 +1069,8 @@ public void head(Node node, int depth) { public void tail(Node node, int depth) { } }, this); - return accum.toString(); + + return StringUtil.releaseBuilder(accum); } /** @@ -1083,9 +1085,9 @@ public void tail(Node node, int depth) { * @see #textNodes() */ public String ownText() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); ownText(sb); - return sb.toString().trim(); + return StringUtil.releaseBuilder(sb).trim(); } private void ownText(StringBuilder accum) { @@ -1172,7 +1174,7 @@ public boolean hasText() { * @see #dataNodes() */ public String data() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); for (Node childNode : childNodes) { if (childNode instanceof DataNode) { @@ -1192,7 +1194,7 @@ public String data() { sb.append(cDataNode.getWholeText()); } } - return sb.toString(); + return StringUtil.releaseBuilder(sb); } /** @@ -1395,9 +1397,10 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) thr * @see #outerHtml() */ public String html() { - StringBuilder accum = StringUtil.stringBuilder(); + StringBuilder accum = StringUtil.borrowBuilder(); html(accum); - return NodeUtils.outputSettings(this).prettyPrint() ? accum.toString().trim() : accum.toString(); + String html = StringUtil.releaseBuilder(accum); + return NodeUtils.outputSettings(this).prettyPrint() ? html.trim() : html; } @Override diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index bbf783f7ce..34ee9f5669 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -149,13 +149,13 @@ public static int codepointsForName(final String name, final int[] codepoints) { public static String escape(String string, Document.OutputSettings out) { if (string == null) return ""; - StringBuilder accum = new StringBuilder(string.length() * 2); + StringBuilder accum = StringUtil.borrowBuilder(); try { escape(accum, string, out, false, false, false); } catch (IOException e) { throw new SerializationException(e); // doesn't happen } - return accum.toString(); + return StringUtil.releaseBuilder(accum); } /** diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 67d9496b8e..aa89c6eda9 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -566,9 +566,9 @@ public Node filter(NodeFilter nodeFilter) { @see Element#text() */ public String outerHtml() { - StringBuilder accum = StringUtil.stringBuilder(); + StringBuilder accum = StringUtil.borrowBuilder(); outerHtml(accum); - return accum.toString(); + return StringUtil.releaseBuilder(accum); } protected void outerHtml(Appendable accum) { diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index 602805bd7b..966f14f8bd 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; +import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; @@ -52,13 +53,13 @@ public String name() { * @return XML declaration */ public String getWholeDeclaration() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); try { getWholeDeclaration(sb, new Document.OutputSettings()); } catch (IOException e) { throw new SerializationException(e); } - return sb.toString().trim(); + return StringUtil.releaseBuilder(sb).trim(); } private void getWholeDeclaration(Appendable accum, Document.OutputSettings out) throws IOException { diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index df7d925b8c..1088d95ad1 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -302,7 +302,7 @@ else if (c.equals(close)) * @return unescaped string */ public static String unescape(String in) { - StringBuilder out = StringUtil.stringBuilder(); + StringBuilder out = StringUtil.borrowBuilder(); char last = 0; for (char c : in.toCharArray()) { if (c == ESC) { @@ -313,7 +313,7 @@ public static String unescape(String in) { out.append(c); last = c; } - return out.toString(); + return StringUtil.releaseBuilder(out); } /** diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index fcf90d4fe0..803a4a4c7e 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -275,7 +275,7 @@ boolean currentNodeInHtmlNS() { * @return unescaped string from reader */ String unescapeEntities(boolean inAttribute) { - StringBuilder builder = StringUtil.stringBuilder(); + StringBuilder builder = StringUtil.borrowBuilder(); while (!reader.isEmpty()) { builder.append(reader.consumeTo('&')); if (reader.matches('&')) { @@ -291,6 +291,6 @@ String unescapeEntities(boolean inAttribute) { } } - return builder.toString(); + return StringUtil.releaseBuilder(builder); } } diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index fcf1ee019d..74116d75e3 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -1,5 +1,6 @@ package org.jsoup.select; +import org.jsoup.helper.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.Element; import org.jsoup.nodes.FormElement; @@ -205,13 +206,13 @@ public Elements val(String value) { * @see #eachText() */ public String text() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); for (Element element : this) { if (sb.length() != 0) sb.append(" "); sb.append(element.text()); } - return sb.toString(); + return StringUtil.releaseBuilder(sb); } /** @@ -251,13 +252,13 @@ public List eachText() { * @see #outerHtml() */ public String html() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); for (Element element : this) { if (sb.length() != 0) sb.append("\n"); sb.append(element.html()); } - return sb.toString(); + return StringUtil.releaseBuilder(sb); } /** @@ -267,13 +268,13 @@ public String html() { * @see #html() */ public String outerHtml() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = StringUtil.borrowBuilder(); for (Element element : this) { if (sb.length() != 0) sb.append("\n"); sb.append(element.outerHtml()); } - return sb.toString(); + return StringUtil.releaseBuilder(sb); } /** diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 99e51473c6..15c0b20bf1 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -131,7 +131,7 @@ else if (combinator == ',') { // group or. } private String consumeSubQuery() { - StringBuilder sq = new StringBuilder(); + StringBuilder sq = StringUtil.borrowBuilder(); while (!tq.isEmpty()) { if (tq.matches("(")) sq.append("(").append(tq.chompBalanced('(', ')')).append(")"); @@ -142,7 +142,7 @@ else if (tq.matchesAny(combinators)) else sq.append(tq.consume()); } - return sq.toString(); + return StringUtil.releaseBuilder(sq); } private void findElements() { From 105f7bdcdbfffc879d0737cf857c02b6823b1b73 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 11 May 2018 18:59:23 -0700 Subject: [PATCH 271/774] Moved StringUtil to the Internal package --- src/main/java/org/jsoup/examples/HtmlToPlainText.java | 2 +- src/main/java/org/jsoup/helper/DataUtil.java | 1 + src/main/java/org/jsoup/helper/HttpConnection.java | 1 + src/main/java/org/jsoup/helper/W3CDom.java | 1 + src/main/java/org/jsoup/{helper => internal}/StringUtil.java | 4 +++- src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/main/java/org/jsoup/nodes/Attributes.java | 2 +- src/main/java/org/jsoup/nodes/Document.java | 2 +- src/main/java/org/jsoup/nodes/DocumentType.java | 2 +- src/main/java/org/jsoup/nodes/Element.java | 2 +- src/main/java/org/jsoup/nodes/Entities.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 2 +- src/main/java/org/jsoup/nodes/TextNode.java | 2 +- src/main/java/org/jsoup/nodes/XmlDeclaration.java | 2 +- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 4 ++-- src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 2 +- src/main/java/org/jsoup/parser/TokenQueue.java | 2 +- src/main/java/org/jsoup/parser/Tokeniser.java | 2 +- src/main/java/org/jsoup/select/CombiningEvaluator.java | 2 +- src/main/java/org/jsoup/select/Elements.java | 2 +- src/main/java/org/jsoup/select/QueryParser.java | 2 +- src/test/java/org/jsoup/integration/UrlConnectTest.java | 2 +- .../java/org/jsoup/integration/servlets/EchoServlet.java | 2 +- .../java/org/jsoup/{helper => internal}/StringUtilTest.java | 5 +++-- src/test/java/org/jsoup/parser/HtmlParserTest.java | 2 +- src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java | 2 +- 26 files changed, 31 insertions(+), 25 deletions(-) rename src/main/java/org/jsoup/{helper => internal}/StringUtil.java (99%) rename src/test/java/org/jsoup/{helper => internal}/StringUtilTest.java (97%) diff --git a/src/main/java/org/jsoup/examples/HtmlToPlainText.java b/src/main/java/org/jsoup/examples/HtmlToPlainText.java index 89ca399095..8a00e79ada 100644 --- a/src/main/java/org/jsoup/examples/HtmlToPlainText.java +++ b/src/main/java/org/jsoup/examples/HtmlToPlainText.java @@ -1,7 +1,7 @@ package org.jsoup.examples; import org.jsoup.Jsoup; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index d09034da76..02b3b25a5b 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -2,6 +2,7 @@ import org.jsoup.UncheckedIOException; import org.jsoup.internal.ConstrainableInputStream; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 7469192538..880e4b473a 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -5,6 +5,7 @@ import org.jsoup.UncheckedIOException; import org.jsoup.UnsupportedMimeTypeException; import org.jsoup.internal.ConstrainableInputStream; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.jsoup.parser.TokenQueue; diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 68475747b1..cc769eb1e2 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -1,5 +1,6 @@ package org.jsoup.helper; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.select.NodeTraversor; diff --git a/src/main/java/org/jsoup/helper/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java similarity index 99% rename from src/main/java/org/jsoup/helper/StringUtil.java rename to src/main/java/org/jsoup/internal/StringUtil.java index c30aa488f7..cbdfc362d3 100644 --- a/src/main/java/org/jsoup/helper/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -1,4 +1,6 @@ -package org.jsoup.helper; +package org.jsoup.internal; + +import org.jsoup.helper.Validate; import java.net.MalformedURLException; import java.net.URL; diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index bca9fe608f..612fdeb209 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index c4e373ce7a..b3fe49a6c7 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 9d6a4aee32..fe389a3807 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -1,6 +1,6 @@ package org.jsoup.nodes; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index ac4090753d..dbc08f37e1 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -1,6 +1,6 @@ package org.jsoup.nodes; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.Document.OutputSettings.Syntax; diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 36a73dfd79..20bc95e866 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; import org.jsoup.helper.ChangeNotifyingArrayList; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Tag; diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 34ee9f5669..d7ce2101e3 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.CharacterReader; import org.jsoup.parser.Parser; diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index aa89c6eda9..0d8424277a 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index dfd0e216b6..fcd81e1a6d 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -1,6 +1,6 @@ package org.jsoup.nodes; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index 966f14f8bd..d1d93058c9 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 9e99d712e0..67a830a5c1 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -1,6 +1,6 @@ package org.jsoup.parser; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; @@ -17,7 +17,7 @@ import java.util.ArrayList; import java.util.List; -import static org.jsoup.helper.StringUtil.inSorted; +import static org.jsoup.internal.StringUtil.inSorted; /** * HTML Tree Builder; creates a DOM from Tokens. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 8b92cdbd8e..656bd508af 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1,6 +1,6 @@ package org.jsoup.parser; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Document; diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 1088d95ad1..854caf28cf 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -1,6 +1,6 @@ package org.jsoup.parser; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; /** diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 803a4a4c7e..657766f1f4 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -1,6 +1,6 @@ package org.jsoup.parser; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.Entities; diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index 94e97c59c1..7ced9e980a 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -1,6 +1,6 @@ package org.jsoup.select; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Element; import java.util.ArrayList; diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 74116d75e3..afa0f02c7c 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -1,6 +1,6 @@ package org.jsoup.select; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.nodes.Element; import org.jsoup.nodes.FormElement; diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 15c0b20bf1..768f70834c 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -1,6 +1,6 @@ package org.jsoup.select; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.TokenQueue; diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index fa5facdb77..8d9c91b606 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -4,7 +4,7 @@ import org.jsoup.HttpStatusException; import org.jsoup.Jsoup; import org.jsoup.UnsupportedMimeTypeException; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.helper.W3CDom; import org.jsoup.nodes.Document; import org.jsoup.nodes.FormElement; diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java index ef2f5e286c..3d357cc47c 100644 --- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -2,7 +2,7 @@ import org.eclipse.jetty.server.Request; import org.jsoup.helper.DataUtil; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.integration.TestServer; import javax.servlet.MultipartConfigElement; diff --git a/src/test/java/org/jsoup/helper/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java similarity index 97% rename from src/test/java/org/jsoup/helper/StringUtilTest.java rename to src/test/java/org/jsoup/internal/StringUtilTest.java index 5af8c23863..d9b9740e00 100644 --- a/src/test/java/org/jsoup/helper/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -1,11 +1,12 @@ -package org.jsoup.helper; +package org.jsoup.internal; import org.jsoup.Jsoup; +import org.jsoup.internal.StringUtil; import org.junit.Test; import java.util.Arrays; -import static org.jsoup.helper.StringUtil.*; +import static org.jsoup.internal.StringUtil.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index e7d689e60f..6503797129 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -2,7 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.integration.ParseTest; import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 08edc75c8a..508b8c8f64 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -2,7 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; -import org.jsoup.helper.StringUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; From e26267dd77b437f9e3ea155ae7a52013fde6c2f5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 11 May 2018 21:20:54 -0700 Subject: [PATCH 272/774] Remove no-longer relevant comment Now that we set .gitattributes --- src/test/java/org/jsoup/integration/ConnectTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index dca0b55218..2d817ba55d 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -302,11 +302,6 @@ public void postFiles() throws IOException { assertEquals("secondPart", ihVal("Part secondPart Name", res)); assertEquals("google-ipod.html", ihVal("Part secondPart Filename", res)); assertEquals("43963", ihVal("Part secondPart Size", res)); - // if this is failing as 43972 it is because git has normalized the html line endings to crlf (windows) - // disable that: - // git config --global core.eol lf - // git config --global core.autocrlf input - // (and rm cached and reset) assertEquals("image/jpeg", ihVal("Part firstPart ContentType", res)); assertEquals("firstPart", ihVal("Part firstPart Name", res)); From 4d104721d96baaa6a6c28d2b472a37f631227f46 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 11 May 2018 21:48:40 -0700 Subject: [PATCH 273/774] Simplify code a bit --- src/main/java/org/jsoup/helper/HttpConnection.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 880e4b473a..fa4ea22d16 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -1114,14 +1114,11 @@ private static void serialiseRequestUrl(Connection.Request req) throws IOExcepti private static boolean needsMultipart(Connection.Request req) { // multipart mode, for files. add the header if we see something with an inputstream, and return a non-null boundary - boolean needsMulti = false; for (Connection.KeyVal keyVal : req.data()) { - if (keyVal.hasInputStream()) { - needsMulti = true; - break; - } + if (keyVal.hasInputStream()) + return true; } - return needsMulti; + return false; } public static class KeyVal implements Connection.KeyVal { From 1933e9727c694863d6b7a9bb98682b560510d212 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 11 May 2018 22:02:08 -0700 Subject: [PATCH 274/774] Make HttpConnection constructor public For DI tasks Fixes #1052 --- src/main/java/org/jsoup/helper/HttpConnection.java | 11 ++++++----- .../java/org/jsoup/helper/HttpConnectionTest.java | 12 ++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index fa4ea22d16..515db0e724 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -76,6 +76,11 @@ public static Connection connect(URL url) { return con; } + public HttpConnection() { + req = new Request(); + res = new Response(); + } + /** * Encodes the input URL into a safe ASCII URL string * @param url unescaped URL @@ -111,11 +116,6 @@ private static String encodeMimeName(String val) { private Connection.Request req; private Connection.Response res; - private HttpConnection() { - req = new Request(); - res = new Response(); - } - public Connection url(URL url) { req.url(url); return this; @@ -704,6 +704,7 @@ static Response execute(Connection.Request req) throws IOException { static Response execute(Connection.Request req, Response previousResponse) throws IOException { Validate.notNull(req, "Request must not be null"); + Validate.notNull(req.url(), "URL must be specified to connect"); String protocol = req.url().getProtocol(); if (!protocol.equals("http") && !protocol.equals("https")) throw new MalformedURLException("Only http & https protocols supported"); diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 94eb4dfc53..4e6bf335f3 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -237,4 +237,16 @@ public class HttpConnectionTest { URL url2 = HttpConnection.encodeUrl(url1); assertEquals("http://test.com/?q=white%20space", url2.toExternalForm()); } + + @Test public void noUrlThrowsValidationError() throws IOException { + HttpConnection con = new HttpConnection(); + boolean threw = false; + try { + con.execute(); + } catch (IllegalArgumentException e) { + threw = true; + assertEquals("URL must be specified to connect", e.getMessage()); + } + assertTrue(threw); + } } From 426ffe7870b937ef9cfa25ebe20e7a478493da99 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 11 May 2018 22:32:07 -0700 Subject: [PATCH 275/774] Simplified nextElements and previousElements impl, added changelog --- CHANGES | 3 ++ src/main/java/org/jsoup/nodes/Element.java | 44 +++++++------------ src/main/java/org/jsoup/select/Elements.java | 8 ++-- .../java/org/jsoup/nodes/ElementTest.java | 18 +++----- 4 files changed, 27 insertions(+), 46 deletions(-) diff --git a/CHANGES b/CHANGES index edf0e86cc9..d969c242f5 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,9 @@ jsoup changelog many pages. + * Improvement: added Element.nextElementSiblings() and Element.previousElementSiblings() + https://github.com/jhy/jsoup/pull/1054 + * Bugfix: when converting a Jsoup document to a W3C DOM, if an element is namespaced but not in a defined namespace, set it to the global namespace. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index dc8ec1c419..b4dcbf2482 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -688,23 +688,12 @@ public Element nextElementSibling() { } /** - * Get all the siblings after this element. + * Get each of the sibling elements that come after this element. * - * @return all the siblings after this element, or empty list if there is no siblings after this + * @return each of the element siblings after this element, or an empty list if there are no next sibling elements */ - public List nextElementSiblings() { - if (parentNode == null) { - return Collections.emptyList(); - } - - List siblings = parent().childElementsList(); - int index = indexInList(this, siblings); - Validate.notNull(index); - - if (siblings.size() > index + 1) { - return siblings.subList(index + 1, siblings.size()); - } - return Collections.emptyList(); + public Elements nextElementSiblings() { + return nextElementSiblings(true); } /** @@ -724,23 +713,20 @@ public Element previousElementSibling() { } /** - * Get all the element siblings before this element. + * Get each of the element siblings before this element. * - * @return all the previous element siblings, or empty list if no previous siblings + * @return the previous element siblings, or an empty list if there are none. */ - public List previousElementSiblings() { - if (parentNode == null) { - return Collections.emptyList(); - } - - List siblings = parent().childElementsList(); - int index = indexInList(this, siblings); - Validate.notNull(index); + public Elements previousElementSiblings() { + return nextElementSiblings(false); + } - if (index > 0 && index < siblings.size()) { - return siblings.subList(0, index); - } - return Collections.emptyList(); + private Elements nextElementSiblings(boolean next) { + Elements els = new Elements(); + if (parentNode == null) + return els; + els.add(this); + return next ? els.nextAll() : els.prevAll(); } /** diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index fcf1ee019d..fe865b8baa 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -510,7 +510,7 @@ public Elements next(String query) { } /** - * Get all of the following element siblings of each element in this list. + * Get each of the following element siblings of each element in this list. * @return all following element siblings. */ public Elements nextAll() { @@ -518,7 +518,7 @@ public Elements nextAll() { } /** - * Get all of the following element siblings of each element in this list, filtered by the query. + * Get each of the following element siblings of each element in this list, that match the query. * @param query CSS query to match siblings against * @return all following element siblings. */ @@ -544,7 +544,7 @@ public Elements prev(String query) { } /** - * Get all of the previous element siblings of each element in this list. + * Get each of the previous element siblings of each element in this list. * @return all previous element siblings. */ public Elements prevAll() { @@ -552,7 +552,7 @@ public Elements prevAll() { } /** - * Get all of the previous element siblings of each element in this list, filtered by the query. + * Get each of the previous element siblings of each element in this list, that match the query. * @param query CSS query to match siblings against * @return all previous element siblings. */ diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index c985eaa105..9219624ec7 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1325,13 +1325,13 @@ public void testNextElementSiblings() { "

  • a
  • " + "
  • b
  • " + "
  • c
  • " + - "" + + " Not An Element but a node" + "
    " + "
  • d
  • " + "
    "); Element element = doc.getElementById("a"); - List elementSiblings = element.nextElementSiblings(); + Elements elementSiblings = element.nextElementSiblings(); assertNotNull(elementSiblings); assertEquals(2, elementSiblings.size()); assertEquals("b", elementSiblings.get(0).id()); @@ -1355,11 +1355,7 @@ public void testNextElementSiblings() { Element div = doc.getElementById("div"); List elementSiblings4 = div.nextElementSiblings(); - try { - Element elementSibling = elementSiblings4.get(0); - fail("This element should has no next siblings"); - } catch (IndexOutOfBoundsException e) { - } + assertEquals(0, elementSiblings4.size()); } @Test @@ -1374,7 +1370,7 @@ public void testPreviousElementSiblings() { ""); Element element = doc.getElementById("b"); - List elementSiblings = element.previousElementSiblings(); + Elements elementSiblings = element.previousElementSiblings(); assertNotNull(elementSiblings); assertEquals(1, elementSiblings.size()); assertEquals("a", elementSiblings.get(0).id()); @@ -1392,10 +1388,6 @@ public void testPreviousElementSiblings() { Element ul = doc.getElementById("ul"); List elementSiblings3 = ul.previousElementSiblings(); - try { - Element element3 = elementSiblings3.get(0); - fail("This element should has no previous siblings"); - } catch (IndexOutOfBoundsException e) { - } + assertEquals(0, elementSiblings3.size()); } } From 220b77140bce70dcf9c767f8f04758b09097db14 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 11 May 2018 22:47:22 -0700 Subject: [PATCH 276/774] Previous elements are returned in reverse order --- src/test/java/org/jsoup/nodes/ElementTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 9219624ec7..e957ef8622 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1383,8 +1383,8 @@ public void testPreviousElementSiblings() { List elementSiblings2 = element2.previousElementSiblings(); assertNotNull(elementSiblings2); assertEquals(2, elementSiblings2.size()); - assertEquals("a", elementSiblings2.get(0).id()); - assertEquals("b", elementSiblings2.get(1).id()); + assertEquals("b", elementSiblings2.get(0).id()); + assertEquals("a", elementSiblings2.get(1).id()); Element ul = doc.getElementById("ul"); List elementSiblings3 = ul.previousElementSiblings(); From db7bc390ea92cb6b60c75d648400fd886b2b4672 Mon Sep 17 00:00:00 2001 From: Ryan Conway Date: Mon, 20 Aug 2018 13:36:20 -0700 Subject: [PATCH 277/774] Add center tag to list of block elements --- src/main/java/org/jsoup/parser/Tag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 0dcbbdb2a6..0af2379916 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -235,7 +235,7 @@ public String toString() { "ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins", "del", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th", "td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main", - "svg", "math" + "svg", "math", "center" }; private static final String[] inlineTags = { "object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd", From cecdb3250c9d3f6cd3e5aed2743afa360e0d8531 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 21 Oct 2018 16:47:37 -0700 Subject: [PATCH 278/774] Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.26.v20180806). --- CHANGES | 2 ++ pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 4af3758a1f..c08f050576 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,8 @@ jsoup changelog set it to the global namespace. + * Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.26.v20180806). + *** Release 1.11.3 [2018-Apr-15] * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are round-tripped into output HTML. diff --git a/pom.xml b/pom.xml index 48e4b42efd..8581b71ec6 100644 --- a/pom.xml +++ b/pom.xml @@ -235,7 +235,7 @@ org.eclipse.jetty jetty-server - 9.2.22.v20170606 + 9.2.26.v20180806 test @@ -243,7 +243,7 @@ org.eclipse.jetty jetty-servlet - 9.2.22.v20170606 + 9.2.26.v20180806 test From 54e87300275b587cd2033ab6493526cbb2caf3e9 Mon Sep 17 00:00:00 2001 From: mboisvert Date: Wed, 5 Dec 2018 17:23:47 -0500 Subject: [PATCH 279/774] Adding unit test demonstrating character reader buffer issue. The CharacterReader re-fills its buffer in the middle of reading a parameter with an underscore ("param_two"). It loses its mark while filling up and drops everything before the underscore in the parameter. --- .../java/org/jsoup/parser/HtmlParserTest.java | 12 + .../htmltests/character-reader-buffer.html | 244 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 src/test/resources/htmltests/character-reader-buffer.html diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 6503797129..b0a252f78b 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1211,4 +1211,16 @@ public void testInvalidTableContents() throws IOException { String html = doc.outerHtml(); assertEquals(" One", TextUtil.stripNewlines(html)); } + + @Test public void characterReaderBuffer() throws IOException { + File in = ParseTest.getFile("/htmltests/character-reader-buffer.html"); + Document doc = Jsoup.parse(in, "UTF-8"); + + String expectedHref = "http://www.domain.com/path?param_one=value¶m_two=value"; + + Elements links = doc.select("a"); + assertEquals(2, links.size()); + assertEquals(expectedHref, links.get(0).attr("href")); // passes + assertEquals(expectedHref, links.get(1).attr("href")); // fails, "but was:<...ath?param_one=value&[]_two-value>" + } } diff --git a/src/test/resources/htmltests/character-reader-buffer.html b/src/test/resources/htmltests/character-reader-buffer.html new file mode 100644 index 0000000000..c367a90652 --- /dev/null +++ b/src/test/resources/htmltests/character-reader-buffer.html @@ -0,0 +1,244 @@ + + +Test + + +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +--------------------------------------------------------------------------------------------------
    +-----------------------------------------------
    +Link one +Link two + + \ No newline at end of file From 49c4a148b7817ed424e8cf823ae601863fec31ce Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 22 Dec 2018 17:17:15 -0800 Subject: [PATCH 280/774] URL cleanup --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8581b71ec6..328230c261 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 2009 GitHub - http://github.com/jhy/jsoup/issues + https://github.com/jhy/jsoup/issues @@ -28,7 +28,7 @@ Jonathan Hedley - http://jonathanhedley.com/ + https://jhy.io/ From 25ba713e1d1a97ebfee38c0df3e7e6d4d9615f97 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 22 Dec 2018 17:28:00 -0800 Subject: [PATCH 281/774] Validate new attribute names correctly Fixes #1159 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Attribute.java | 3 ++- src/test/java/org/jsoup/nodes/AttributeTest.java | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c08f050576..e457b91dfb 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,10 @@ jsoup changelog set it to the global namespace. + * Bugfix: attributes created with the Attribute constructor with just spaces for names would incorrectly pass + validation. + + * Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.26.v20180806). *** Release 1.11.3 [2018-Apr-15] diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 612fdeb209..e321b6aaf0 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -41,8 +41,9 @@ public Attribute(String key, String value) { * @see #createFromEncoded*/ public Attribute(String key, String val, Attributes parent) { Validate.notNull(key); - this.key = key.trim(); + key = key.trim(); Validate.notEmpty(key); // trimming could potentially make empty, so validate here + this.key = key; this.val = val; this.parent = parent; } diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 4789a006f8..651019eda9 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -17,4 +17,13 @@ public class AttributeTest { assertEquals(s + "=\"A" + s + "B\"", attr.html()); assertEquals(attr.html(), attr.toString()); } + + @Test(expected = IllegalArgumentException.class) public void validatesKeysNotEmpty() { + Attribute attr = new Attribute(" ", "Check"); + } + + @Test(expected = IllegalArgumentException.class) public void validatesKeysNotEmptyViaSet() { + Attribute attr = new Attribute("One", "Check"); + attr.setKey(" "); + } } From 973234bc842b0de2febea195f7819236b57fd992 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 22 Dec 2018 17:59:13 -0800 Subject: [PATCH 282/774] Added ParserTest Trying to validate #1152, no repro --- .../java/org/jsoup/nodes/EntitiesTest.java | 2 +- .../java/org/jsoup/parser/ParserTest.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/jsoup/parser/ParserTest.java diff --git a/src/test/java/org/jsoup/nodes/EntitiesTest.java b/src/test/java/org/jsoup/nodes/EntitiesTest.java index c7ba4247a9..47f5e77610 100644 --- a/src/test/java/org/jsoup/nodes/EntitiesTest.java +++ b/src/test/java/org/jsoup/nodes/EntitiesTest.java @@ -31,7 +31,7 @@ public class EntitiesTest { assertEquals(text, Entities.unescape(escapedUtfMin)); } - @Test public void escapedSupplemtary() { + @Test public void escapedSupplementary() { String text = "\uD835\uDD59"; String escapedAscii = Entities.escape(text, new OutputSettings().charset("ascii").escapeMode(base)); assertEquals("𝕙", escapedAscii); diff --git a/src/test/java/org/jsoup/parser/ParserTest.java b/src/test/java/org/jsoup/parser/ParserTest.java new file mode 100644 index 0000000000..6ab9e70377 --- /dev/null +++ b/src/test/java/org/jsoup/parser/ParserTest.java @@ -0,0 +1,25 @@ +package org.jsoup.parser; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ParserTest { + + @Test + public void unescapeEntities() { + String s = Parser.unescapeEntities("One & Two", false); + assertEquals("One & Two", s); + } + + @Test + public void unescapeEntitiesHandlesLargeInput() { + StringBuilder longBody = new StringBuilder(500000); + do { + longBody.append("SomeNonEncodedInput"); + } while (longBody.length() < 64 * 1024); + + String body = longBody.toString(); + assertEquals(body, Parser.unescapeEntities(body, false)); + } +} From 38c13b5ae97c294afb859c49ded903beb7b9b100 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 22 Dec 2018 18:19:31 -0800 Subject: [PATCH 283/774] Correct check for element children Fixes #1139 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Comment.java | 2 +- src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e457b91dfb..41c790d978 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,10 @@ jsoup changelog validation. + * Bugfix: some pseudo XML Declarations were incorrectly handled when using the XML Parser, leading to an IOOB + exception when parsing. + + * Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.26.v20180806). *** Release 1.11.3 [2018-Apr-15] diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index d0669fef4f..d9833854e8 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -75,7 +75,7 @@ public XmlDeclaration asXmlDeclaration() { String data = getData(); Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri(), Parser.xmlParser()); XmlDeclaration decl = null; - if (doc.childNodeSize() > 0) { + if (doc.children().size() > 0) { Element el = doc.child(0); decl = new XmlDeclaration(NodeUtils.parser(doc).settings().normalizeTag(el.tagName()), data.startsWith("!")); decl.attributes().addAll(el.attributes()); diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 508b8c8f64..7ce70b3f7c 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -237,4 +237,12 @@ public void handlesDodgyXmlDecl() { Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); assertEquals("One", doc.select("val").text()); } + + @Test + public void handlesLTinScript() { + // https://github.com/jhy/jsoup/issues/1139 + String html = ""; + Document doc = Jsoup.parse(html, "", Parser.xmlParser()); + assertEquals("", doc.html()); // converted from pseudo xmldecl to comment + } } From 7ff7c43e9fbf4bbaf2b4517b3d4f8a429d87d3bb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 22 Dec 2018 19:56:49 -0800 Subject: [PATCH 284/774] Use normalized element names for tree searching Fixes #1149 --- CHANGES | 4 + src/main/java/org/jsoup/nodes/Element.java | 14 +++- .../org/jsoup/parser/HtmlTreeBuilder.java | 28 +++---- .../jsoup/parser/HtmlTreeBuilderState.java | 82 +++++++++---------- .../java/org/jsoup/parser/ParseSettings.java | 6 ++ src/main/java/org/jsoup/parser/Tag.java | 11 +++ src/main/java/org/jsoup/parser/Token.java | 4 +- .../java/org/jsoup/parser/HtmlParserTest.java | 8 ++ 8 files changed, 99 insertions(+), 58 deletions(-) diff --git a/CHANGES b/CHANGES index 41c790d978..520bdfe051 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,10 @@ jsoup changelog * Improvement: added Element.nextElementSiblings() and Element.previousElementSiblings() https://github.com/jhy/jsoup/pull/1054 + * Bugfix: when using the tag case preserving parsing settings, certain HTML tree building rules where not followed + for upper case tags. + + * Bugfix: when converting a Jsoup document to a W3C DOM, if an element is namespaced but not in a defined namespace, set it to the global namespace. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 850e724749..efbfced1cc 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1,8 +1,8 @@ package org.jsoup.nodes; import org.jsoup.helper.ChangeNotifyingArrayList; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Tag; import org.jsoup.select.Collector; @@ -123,7 +123,8 @@ public String nodeName() { } /** - * Get the name of the tag for this element. E.g. {@code div} + * Get the name of the tag for this element. E.g. {@code div}. If you are using {@link ParseSettings#preserveCase + * case preserving parsing}, this will return the source's original case. * * @return the tag name */ @@ -131,6 +132,15 @@ public String tagName() { return tag.getName(); } + /** + * Get the normalized name of this Element's tag. This will always be the lowercased version of the tag, regardless + * of the tag case preserving setting of the parser. + * @return + */ + public String normalName() { + return tag.normalName(); + } + /** * Change the tag of this element. For example, convert a {@code } to a {@code
    } with * {@code el.tagName("div");}. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 67a830a5c1..bea598dea6 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -312,7 +312,7 @@ private boolean isElementInQueue(ArrayList queue, Element element) { Element getFromStack(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); - if (next.nodeName().equals(elName)) { + if (next.normalName().equals(elName)) { return next; } } @@ -334,7 +334,7 @@ void popStackToClose(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); stack.remove(pos); - if (next.nodeName().equals(elName)) + if (next.normalName().equals(elName)) break; } } @@ -344,7 +344,7 @@ void popStackToClose(String... elNames) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); stack.remove(pos); - if (inSorted(next.nodeName(), elNames)) + if (inSorted(next.normalName(), elNames)) break; } } @@ -352,7 +352,7 @@ void popStackToClose(String... elNames) { void popStackToBefore(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); - if (next.nodeName().equals(elName)) { + if (next.normalName().equals(elName)) { break; } else { stack.remove(pos); @@ -375,7 +375,7 @@ void clearStackToTableRowContext() { private void clearStackToContext(String... nodeNames) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); - if (StringUtil.in(next.nodeName(), nodeNames) || next.nodeName().equals("html")) + if (StringUtil.in(next.normalName(), nodeNames) || next.normalName().equals("html")) break; else stack.remove(pos); @@ -417,7 +417,7 @@ void resetInsertionMode() { last = true; node = contextElement; } - String name = node.nodeName(); + String name = node.normalName(); if ("select".equals(name)) { transition(HtmlTreeBuilderState.InSelect); break; // frag @@ -473,7 +473,7 @@ private boolean inSpecificScope(String[] targetNames, String[] baseTypes, String // don't walk too far up the tree for (int pos = bottom; pos >= top; pos--) { - final String elName = stack.get(pos).nodeName(); + final String elName = stack.get(pos).normalName(); if (inSorted(elName, targetNames)) return true; if (inSorted(elName, baseTypes)) @@ -514,7 +514,7 @@ boolean inTableScope(String targetName) { boolean inSelectScope(String targetName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element el = stack.get(pos); - String elName = el.nodeName(); + String elName = el.normalName(); if (elName.equals(targetName)) return true; if (!inSorted(elName, TagSearchSelectScope)) // all elements except @@ -566,8 +566,8 @@ List getPendingTableCharacters() { process, then the UA must perform the above steps as if that element was not in the above list. */ void generateImpliedEndTags(String excludeTag) { - while ((excludeTag != null && !currentElement().nodeName().equals(excludeTag)) && - inSorted(currentElement().nodeName(), TagSearchEndTags)) + while ((excludeTag != null && !currentElement().normalName().equals(excludeTag)) && + inSorted(currentElement().normalName(), TagSearchEndTags)) pop(); } @@ -578,7 +578,7 @@ void generateImpliedEndTags() { boolean isSpecial(Element el) { // todo: mathml's mi, mo, mn // todo: svg's foreigObject, desc, title - String name = el.nodeName(); + String name = el.normalName(); return inSorted(name, TagSearchSpecial); } @@ -615,7 +615,7 @@ void pushActiveFormattingElements(Element in) { private boolean isSameFormattingElement(Element a, Element b) { // same if: same namespace, tag, and attributes. Element.equals only checks tag, might in future check children - return a.nodeName().equals(b.nodeName()) && + return a.normalName().equals(b.normalName()) && // a.namespace().equals(b.namespace()) && a.attributes().equals(b.attributes()); // todo: namespaces @@ -646,7 +646,7 @@ void reconstructFormattingElements() { // 8. create new element from element, 9 insert into current node, onto stack skip = false; // can only skip increment from 4. - Element newEl = insertStartTag(entry.nodeName()); // todo: avoid fostering here? + Element newEl = insertStartTag(entry.normalName()); // todo: avoid fostering here? // newEl.namespace(entry.namespace()); // todo: namespaces newEl.attributes().addAll(entry.attributes()); @@ -686,7 +686,7 @@ Element getActiveFormattingElement(String nodeName) { Element next = formattingElements.get(pos); if (next == null) // scope marker break; - else if (next.nodeName().equals(nodeName)) + else if (next.normalName().equals(nodeName)) return next; } return null; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 656bd508af..a5532c744a 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -312,11 +312,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { ArrayList stack = tb.getStack(); for (int i = stack.size() - 1; i > 0; i--) { Element el = stack.get(i); - if (el.nodeName().equals("li")) { + if (el.normalName().equals("li")) { tb.processEndTag("li"); break; } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.nodeName(), Constants.InBodyStartLiBreakers)) + if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; } if (tb.inButtonScope("p")) { @@ -336,7 +336,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (name.equals("body")) { tb.error(this); ArrayList stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).nodeName().equals("body"))) { + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { // only in fragment case return false; // ignore } else { @@ -350,7 +350,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (name.equals("frameset")) { tb.error(this); ArrayList stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).nodeName().equals("body"))) { + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { // only in fragment case return false; // ignore } else if (!tb.framesetOk()) { @@ -369,7 +369,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } - if (StringUtil.inSorted(tb.currentElement().nodeName(), Constants.Headings)) { + if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { tb.error(this); tb.pop(); } @@ -395,11 +395,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { ArrayList stack = tb.getStack(); for (int i = stack.size() - 1; i > 0; i--) { Element el = stack.get(i); - if (StringUtil.inSorted(el.nodeName(), Constants.DdDt)) { - tb.processEndTag(el.nodeName()); + if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { + tb.processEndTag(el.normalName()); break; } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.nodeName(), Constants.InBodyStartLiBreakers)) + if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; } if (tb.inButtonScope("p")) { @@ -528,14 +528,14 @@ boolean process(Token t, HtmlTreeBuilder tb) { else tb.transition(InSelect); } else if (StringUtil.inSorted(name, Constants.InBodyStartOptions)) { - if (tb.currentElement().nodeName().equals("option")) + if (tb.currentElement().normalName().equals("option")) tb.processEndTag("option"); tb.reconstructFormattingElements(); tb.insert(startTag); } else if (StringUtil.inSorted(name, Constants.InBodyStartRuby)) { if (tb.inScope("ruby")) { tb.generateImpliedEndTags(); - if (!tb.currentElement().nodeName().equals("ruby")) { + if (!tb.currentElement().normalName().equals("ruby")) { tb.error(this); tb.popStackToBefore("ruby"); // i.e. close up to but not include name } @@ -571,7 +571,7 @@ else if (!tb.onStack(formatEl)) { tb.error(this); tb.removeFromActiveFormattingElements(formatEl); return true; - } else if (!tb.inScope(formatEl.nodeName())) { + } else if (!tb.inScope(formatEl.normalName())) { tb.error(this); return false; } else if (tb.currentElement() != formatEl) @@ -595,7 +595,7 @@ else if (!tb.onStack(formatEl)) { } } if (furthestBlock == null) { - tb.popStackToClose(formatEl.nodeName()); + tb.popStackToClose(formatEl.normalName()); tb.removeFromActiveFormattingElements(formatEl); return true; } @@ -630,7 +630,7 @@ else if (!tb.onStack(formatEl)) { lastNode = node; } - if (StringUtil.inSorted(commonAncestor.nodeName(), Constants.InBodyEndTableFosters)) { + if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { if (lastNode.parent() != null) lastNode.remove(); tb.insertInFosterParent(lastNode); @@ -659,7 +659,7 @@ else if (!tb.onStack(formatEl)) { return false; } else { tb.generateImpliedEndTags(); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } @@ -672,7 +672,7 @@ else if (!tb.onStack(formatEl)) { return false; } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } @@ -696,7 +696,7 @@ else if (!tb.onStack(formatEl)) { return false; } else { tb.generateImpliedEndTags(); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); // remove currentForm from stack. will shift anything under up. tb.removeFromStack(currentForm); @@ -708,7 +708,7 @@ else if (!tb.onStack(formatEl)) { return tb.process(endTag); } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } @@ -718,7 +718,7 @@ else if (!tb.onStack(formatEl)) { return false; } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } @@ -728,7 +728,7 @@ else if (!tb.onStack(formatEl)) { return false; } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(Constants.Headings); } @@ -742,7 +742,7 @@ else if (!tb.onStack(formatEl)) { return false; } tb.generateImpliedEndTags(); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); @@ -765,13 +765,13 @@ else if (!tb.onStack(formatEl)) { } boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { - String name = tb.settings.normalizeTag(t.asEndTag().name()); // matches with case sensitivity if enabled + String name = t.asEndTag().normalName; // case insensitive search - goal is to preserve output case, not for the parse to be case sensitive ArrayList stack = tb.getStack(); for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); - if (node.nodeName().equals(name)) { + if (node.normalName().equals(name)) { tb.generateImpliedEndTags(name); - if (!name.equals(tb.currentElement().nodeName())) + if (!name.equals(tb.currentElement().normalName())) tb.error(this); tb.popStackToClose(name); break; @@ -884,7 +884,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } return true; // todo: as above todo } else if (t.isEOF()) { - if (tb.currentElement().nodeName().equals("html")) + if (tb.currentElement().normalName().equals("html")) tb.error(this); return true; // stops parsing } @@ -894,7 +894,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.error(this); boolean processed; - if (StringUtil.in(tb.currentElement().nodeName(), "table", "tbody", "tfoot", "thead", "tr")) { + if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { tb.setFosterInserts(true); processed = tb.process(t, InBody); tb.setFosterInserts(false); @@ -923,7 +923,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (!isWhitespace(character)) { // InTable anything else section: tb.error(this); - if (StringUtil.in(tb.currentElement().nodeName(), "table", "tbody", "tfoot", "thead", "tr")) { + if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { tb.setFosterInserts(true); tb.process(new Token.Character().data(character), InBody); tb.setFosterInserts(false); @@ -951,7 +951,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(); - if (!tb.currentElement().nodeName().equals("caption")) + if (!tb.currentElement().normalName().equals("caption")) tb.error(this); tb.popStackToClose("caption"); tb.clearFormattingElementsToLastMarker(); @@ -1004,7 +1004,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { case EndTag: Token.EndTag endTag = t.asEndTag(); if (endTag.normalName.equals("colgroup")) { - if (tb.currentElement().nodeName().equals("html")) { // frag case + if (tb.currentElement().normalName().equals("html")) { // frag case tb.error(this); return false; } else { @@ -1015,7 +1015,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return anythingElse(t, tb); break; case EOF: - if (tb.currentElement().nodeName().equals("html")) + if (tb.currentElement().normalName().equals("html")) return true; // stop parsing; frag case else return anythingElse(t, tb); @@ -1086,7 +1086,7 @@ private boolean exitTableBody(Token t, HtmlTreeBuilder tb) { return false; } tb.clearStackToTableBodyContext(); - tb.processEndTag(tb.currentElement().nodeName()); // tbody, tfoot, thead + tb.processEndTag(tb.currentElement().normalName()); // tbody, tfoot, thead return tb.process(t); } @@ -1170,7 +1170,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } tb.generateImpliedEndTags(); - if (!tb.currentElement().nodeName().equals(name)) + if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); @@ -1237,13 +1237,13 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (name.equals("html")) return tb.process(start, InBody); else if (name.equals("option")) { - if (tb.currentElement().nodeName().equals("option")) + if (tb.currentElement().normalName().equals("option")) tb.processEndTag("option"); tb.insert(start); } else if (name.equals("optgroup")) { - if (tb.currentElement().nodeName().equals("option")) + if (tb.currentElement().normalName().equals("option")) tb.processEndTag("option"); - else if (tb.currentElement().nodeName().equals("optgroup")) + else if (tb.currentElement().normalName().equals("optgroup")) tb.processEndTag("optgroup"); tb.insert(start); } else if (name.equals("select")) { @@ -1266,15 +1266,15 @@ else if (tb.currentElement().nodeName().equals("optgroup")) name = end.normalName(); switch (name) { case "optgroup": - if (tb.currentElement().nodeName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).nodeName().equals("optgroup")) + if (tb.currentElement().normalName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).normalName().equals("optgroup")) tb.processEndTag("option"); - if (tb.currentElement().nodeName().equals("optgroup")) + if (tb.currentElement().normalName().equals("optgroup")) tb.pop(); else tb.error(this); break; case "option": - if (tb.currentElement().nodeName().equals("option")) + if (tb.currentElement().normalName().equals("option")) tb.pop(); else tb.error(this); @@ -1293,7 +1293,7 @@ else if (tb.currentElement().nodeName().equals("optgroup")) } break; case EOF: - if (!tb.currentElement().nodeName().equals("html")) + if (!tb.currentElement().normalName().equals("html")) tb.error(this); break; default: @@ -1380,17 +1380,17 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } } else if (t.isEndTag() && t.asEndTag().normalName().equals("frameset")) { - if (tb.currentElement().nodeName().equals("html")) { // frag + if (tb.currentElement().normalName().equals("html")) { // frag tb.error(this); return false; } else { tb.pop(); - if (!tb.isFragmentParsing() && !tb.currentElement().nodeName().equals("frameset")) { + if (!tb.isFragmentParsing() && !tb.currentElement().normalName().equals("frameset")) { tb.transition(AfterFrameset); } } } else if (t.isEOF()) { - if (!tb.currentElement().nodeName().equals("html")) { + if (!tb.currentElement().normalName().equals("html")) { tb.error(this); return true; } diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index 71bb4ce1ae..b12c3f2943 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -35,6 +35,9 @@ public ParseSettings(boolean tag, boolean attribute) { preserveAttributeCase = attribute; } + /** + * Normalizes a tag name according to the case preservation setting. + */ public String normalizeTag(String name) { name = name.trim(); if (!preserveTagCase) @@ -42,6 +45,9 @@ public String normalizeTag(String name) { return name; } + /** + * Normalizes an attribute according to the case preservation setting. + */ public String normalizeAttribute(String name) { name = name.trim(); if (!preserveAttributeCase) diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 0dcbbdb2a6..180857231e 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -1,6 +1,7 @@ package org.jsoup.parser; import org.jsoup.helper.Validate; +import org.jsoup.internal.Normalizer; import java.util.HashMap; import java.util.Map; @@ -14,6 +15,7 @@ public class Tag { private static final Map tags = new HashMap<>(); // map of known tags private String tagName; + private String normalName; // always the lower case version of this tag, regardless of case preservation mode private boolean isBlock = true; // block or inline private boolean formatAsBlock = true; // should be formatted as a block private boolean canContainInline = true; // only pcdata if not @@ -25,6 +27,7 @@ public class Tag { private Tag(String tagName) { this.tagName = tagName; + normalName = Normalizer.lowerCase(tagName); } /** @@ -36,6 +39,14 @@ public String getName() { return tagName; } + /** + * Get this tag's normalized (lowercased) name. + * @return the tag's normal name. + */ + public String normalName() { + return normalName; + } + /** * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything. *

    diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index e1fde43325..55866ed18d 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -130,12 +130,14 @@ final void finaliseTag() { } } + /** Preserves case */ final String name() { // preserves case, for input into Tag.valueOf (which may drop case) Validate.isFalse(tagName == null || tagName.length() == 0); return tagName; } - final String normalName() { // loses case, used in tree building for working out where in tree it should go + /** Lower case */ + final String normalName() { // lower case, used in tree building for working out where in tree it should go return normalName; } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 6503797129..0003a9e7b0 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1125,6 +1125,14 @@ public void testInvalidTableContents() throws IOException { assertEquals(" A B ", StringUtil.normaliseWhitespace(doc.body().html())); } + @Test public void preservedCaseLinksCantNest() { + String html = "ONE Two"; + Document doc = Parser.htmlParser() + .settings(ParseSettings.preserveCase) + .parseInput(html, ""); + assertEquals(" ONE Two ", StringUtil.normaliseWhitespace(doc.body().html())); + } + @Test public void normalizesDiscordantTags() { Document document = Jsoup.parse("

    test

    "); assertEquals("
    \n test\n
    \n

    ", document.body().html()); From 3ffb622c48c01875448701075637c1f1c0a0b586 Mon Sep 17 00:00:00 2001 From: offa Date: Sun, 23 Dec 2018 05:19:26 +0000 Subject: [PATCH 285/774] Oracle JDK10 and JDK11 build added to Travis (#1124) * Oracle JDK10 build added. * Oracle JDK 11 ci build added. * Oracle has deprecated JDK10, OpenJDK10 is used instead now. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5d35860d78..f303206a8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ jdk: - openjdk7 - oraclejdk8 - oraclejdk9 + - openjdk10 + - oraclejdk11 cache: directories: From 895367092ec5567f97a7cd1ba2ce616507cc0978 Mon Sep 17 00:00:00 2001 From: Nathanael Yang <41705526+ny83427@users.noreply.github.com> Date: Sat, 22 Dec 2018 21:40:36 -0800 Subject: [PATCH 286/774] Optimize toArray via change length to 0 directly (#1158) --- src/main/java/org/jsoup/nodes/Element.java | 6 +++--- src/main/java/org/jsoup/nodes/Node.java | 4 ++-- src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 2 +- src/main/java/org/jsoup/parser/Parser.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index efbfced1cc..6ac9b2fa59 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -460,7 +460,7 @@ public Element insertChildren(int index, Collection children) { Validate.isTrue(index >= 0 && index <= currentSize, "Insert position out of bounds."); ArrayList nodes = new ArrayList<>(children); - Node[] nodeArray = nodes.toArray(new Node[nodes.size()]); + Node[] nodeArray = nodes.toArray(new Node[0]); addChildren(index, nodeArray); return this; } @@ -545,7 +545,7 @@ public Element prependText(String text) { public Element append(String html) { Validate.notNull(html); List nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri()); - addChildren(nodes.toArray(new Node[nodes.size()])); + addChildren(nodes.toArray(new Node[0])); return this; } @@ -558,7 +558,7 @@ public Element append(String html) { public Element prepend(String html) { Validate.notNull(html); List nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri()); - addChildren(0, nodes.toArray(new Node[nodes.size()])); + addChildren(0, nodes.toArray(new Node[0])); return this; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 0d8424277a..eb64669cb3 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -230,7 +230,7 @@ public List childNodesCopy() { public abstract int childNodeSize(); protected Node[] childNodesAsArray() { - return ensureChildNodes().toArray(new Node[childNodeSize()]); + return ensureChildNodes().toArray(new Node[0]); } /** @@ -333,7 +333,7 @@ private void addSiblingHtml(int index, String html) { Element context = parent() instanceof Element ? (Element) parent() : null; List nodes = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); - parentNode.addChildren(index, nodes.toArray(new Node[nodes.size()])); + parentNode.addChildren(index, nodes.toArray(new Node[0])); } /** diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index a5532c744a..dcde12e1be 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -642,7 +642,7 @@ else if (!tb.onStack(formatEl)) { Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); adopter.attributes().addAll(formatEl.attributes()); - Node[] childNodes = furthestBlock.childNodes().toArray(new Node[furthestBlock.childNodeSize()]); + Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); for (Node childNode : childNodes) { adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. } diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index c544d18740..be6d45ffa7 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -164,7 +164,7 @@ public static Document parseBodyFragment(String bodyHtml, String baseUri) { Document doc = Document.createShell(baseUri); Element body = doc.body(); List nodeList = parseFragment(bodyHtml, body, baseUri); - Node[] nodes = nodeList.toArray(new Node[nodeList.size()]); // the node list gets modified when re-parented + Node[] nodes = nodeList.toArray(new Node[0]); // the node list gets modified when re-parented for (int i = nodes.length - 1; i > 0; i--) { nodes[i].remove(); } From 952b0e85cc70fedf7c11d75d1c262b73e51b6a5b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Dec 2018 16:20:26 -0800 Subject: [PATCH 287/774] Typo --- src/main/java/org/jsoup/parser/Tokeniser.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 657766f1f4..22e1ce1d5e 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -181,7 +181,7 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in if (!found) { reader.rewindToMark(); if (looksLegit) // named with semicolon - characterReferenceError(String.format("invalid named referenece '%s'", nameRef)); + characterReferenceError(String.format("invalid named reference '%s'", nameRef)); return null; } if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny('=', '-', '_'))) { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index b0a252f78b..4ec68b9b93 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -810,7 +810,7 @@ public class HtmlParserTest { assertEquals(5, errors.size()); assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("36: Invalid character reference: invalid named referenece 'arrgh'", errors.get(2).toString()); + assertEquals("36: Invalid character reference: invalid named reference 'arrgh'", errors.get(2).toString()); assertEquals("50: Tag cannot be self closing; not a void tag", errors.get(3).toString()); assertEquals("61: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(4).toString()); } @@ -824,7 +824,7 @@ public class HtmlParserTest { assertEquals(3, errors.size()); assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("36: Invalid character reference: invalid named referenece 'arrgh'", errors.get(2).toString()); + assertEquals("36: Invalid character reference: invalid named reference 'arrgh'", errors.get(2).toString()); } @Test public void noErrorsByDefault() { From 54a8d301bb486fcc87887d2961da0e82b55dd54e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Dec 2018 16:45:01 -0800 Subject: [PATCH 288/774] Do a full bufferUp when marking a reader This makes sure there is enough buffer to rewind --- .../org/jsoup/parser/CharacterReader.java | 13 ++++++-- .../org/jsoup/parser/CharacterReaderTest.java | 30 ++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 1bc6092944..456c10449c 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -24,7 +24,7 @@ public final class CharacterReader { private int bufSplitPoint; private int bufPos; private int readerPos; - private int bufMark; + private int bufMark = -1; private final String[] stringCache = new String[512]; // holds reused strings in this doc, to lessen garbage public CharacterReader(Reader input, int sz) { @@ -57,7 +57,7 @@ private void bufferUp() { bufLength = read; readerPos += pos; bufPos = 0; - bufMark = 0; + bufMark = -1; bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; } } catch (IOException e) { @@ -103,6 +103,9 @@ char consume() { } void unconsume() { + if (bufPos < 1) + throw new UncheckedIOException(new IOException("No buffer left to unconsume")); + bufPos--; } @@ -114,10 +117,16 @@ public void advance() { } void mark() { + // extra buffer up, to get as much rewind capacity as possible + bufSplitPoint = 0; + bufferUp(); bufMark = bufPos; } void rewindToMark() { + if (bufMark == -1) + throw new UncheckedIOException(new IOException("Mark invalid")); + bufPos = bufMark; } diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index ad9e32b02f..3619e6424a 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -1,11 +1,13 @@ package org.jsoup.parser; -import org.junit.Ignore; import org.junit.Test; +import java.io.BufferedReader; import java.io.StringReader; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Test suite for character reader. @@ -272,7 +274,6 @@ public void consumeToNonexistentEndWhenAtAnd() { assertTrue(r.isEmpty()); } - @Ignore @Test public void notEmptyAtBufferSplitPoint() { CharacterReader r = new CharacterReader(new StringReader("How about now"), 3); @@ -281,8 +282,29 @@ public void notEmptyAtBufferSplitPoint() { assertEquals(' ', r.consume()); assertFalse(r.isEmpty()); + } + + @Test public void bufferUp() { + String note = "HelloThere"; // + ! = 11 chars + int loopCount = 64; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < loopCount; i++) { + sb.append(note); + sb.append("!"); + } + + String s = sb.toString(); + BufferedReader br = new BufferedReader(new StringReader(s)); + + CharacterReader r = new CharacterReader(br); + for (int i = 0; i < loopCount; i++) { + String pull = r.consumeTo('!'); + assertEquals(note, pull); + assertEquals('!', r.current()); + r.advance(); + } - // todo - current consume to won't expand buffer. impl buffer extension and test + assertTrue(r.isEmpty()); } From 1be6e5c1cc868e5cfbd5937db4e926717fe5904c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Dec 2018 16:52:40 -0800 Subject: [PATCH 289/774] Changelog for #1154 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 520bdfe051..edaaee5f3b 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,10 @@ jsoup changelog exception when parsing. + * Bugfix: when parsing URL parameter names in an attribute that is not correctly HTML encoded, and near the end of the + current buffer, those parameters may be incorrectly dropped. (Improved CharacterReader mark/reset support.) + + * Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.26.v20180806). *** Release 1.11.3 [2018-Apr-15] From 9934dfc6f514881940256bd1605f86fcdb245cf2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Dec 2018 20:20:13 -0800 Subject: [PATCH 290/774] Add build status to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a2ed057b0b..b77b2a2a96 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ jsoup is designed to deal with all varieties of HTML found in the wild; from pri See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API documentation](https://jsoup.org/apidocs/). +[![Build Status](https://travis-ci.org/jhy/jsoup.svg?branch=master)](https://travis-ci.org/jhy/jsoup) + ## Example Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html): From 8adcd55d8abe4c84ae2dcffb9a882abc4143bb93 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Dec 2018 20:30:47 -0800 Subject: [PATCH 291/774] Chagelog tweak --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index edaaee5f3b..932e686811 100644 --- a/CHANGES +++ b/CHANGES @@ -7,14 +7,14 @@ jsoup changelog updates. * Improvement: documents now remember their parser, so when later manipulating them, the correct HTML or XML tree - builder is reused, as are the parser settings like case sensitivity. + builder is reused, as are the parser settings like case preservation. * Improvement: Jsoup now detects the character set of the input if specified in an XML Declaration, when using the HTML parser. Previously that only happened when the XML parser was specified. - * Improvement: if the document's input character does not support encoding, flip it to one that does. + * Improvement: if the document's input character set does not support encoding, flip it to one that does. * Improvement: if a start tag is missing a > and a new tag is seen with a <, treat that as a new tag. (This differs From 8b837a43cbe2c12624ab2088dc4ff9a725af5f4d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 23 Dec 2018 22:02:59 -0800 Subject: [PATCH 292/774] Return null attribute values as empty string Previous / defined behaviour. Fixes #1065 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/test/java/org/jsoup/nodes/AttributeTest.java | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 932e686811..2810ad0c32 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,10 @@ jsoup changelog current buffer, those parameters may be incorrectly dropped. (Improved CharacterReader mark/reset support.) + * Bugfix: boolean attribute values would be returned as null, vs an empty string, when accessed via the + Attribute#getValue() method. + + * Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.26.v20180806). *** Release 1.11.3 [2018-Apr-15] diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index e321b6aaf0..6fab394406 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -77,7 +77,7 @@ public void setKey(String key) { @return the attribute value */ public String getValue() { - return val; + return Attributes.checkNotNull(val); } /** diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 651019eda9..1746963295 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -1,5 +1,6 @@ package org.jsoup.nodes; +import org.jsoup.Jsoup; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -26,4 +27,14 @@ public class AttributeTest { Attribute attr = new Attribute("One", "Check"); attr.setKey(" "); } + + @Test public void booleanAttributesAreEmptyStringValues() { + Document doc = Jsoup.parse("
    :last-childelements that are the last child of some other element.ol {@literal >} li:last-child
    :first-of-typeelements that are the first sibling of its type in the list of children of its parent elementdl dt:first-of-type
    :last-of-typeelements that are the last sibling of its type in the list of children of its parent elementtr {@literal >} td:last-of-type
    :only-childelements that have a parent element and whose parent element hasve no other element children
    :only-childelements that have a parent element and whose parent element have no other element children
    :only-of-type an element that has a parent element and whose parent element has no other element children with the same expanded element name
    :emptyelements that have no children at all
    From a3649e6b1cb55c7cbb47870a802bbc6419b74f45 Mon Sep 17 00:00:00 2001 From: Anders Eriksson Date: Wed, 27 Mar 2019 13:11:02 +0100 Subject: [PATCH 311/774] fix inconsistent javadoc Element.getElementsMatchingOwnText(String regex) did not specify it only matches on "own text" making docs a bit confusing when skimming through. --- src/main/java/org/jsoup/nodes/Element.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 78fbd8446f..982aea6453 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1021,7 +1021,7 @@ public Elements getElementsMatchingOwnText(Pattern pattern) { } /** - * Find elements whose text matches the supplied regular expression. + * Find elements whose own text matches the supplied regular expression. * @param regex regular expression to match text against. You can use embedded flags (such as (?i) and (?m) to control regex options. * @return elements matching the supplied regular expression. * @see Element#ownText() From ff979f461572acfc32c0c70b091cc3a24529296a Mon Sep 17 00:00:00 2001 From: danglotb Date: Fri, 10 Aug 2018 11:20:32 +0200 Subject: [PATCH 312/774] test: chompBalanced() should throw IllegalArgumentException when unbalanced input --- src/test/java/org/jsoup/parser/TokenQueueTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java index f169552c62..1ed9a41ec4 100644 --- a/src/test/java/org/jsoup/parser/TokenQueueTest.java +++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java @@ -82,4 +82,16 @@ public class TokenQueueTest { private static void validateNestedQuotes(String html, String selector) { assertEquals("#identifier", Jsoup.parse(html).select(selector).first().cssSelector()); } + + @Test + public void chompBalancedThrowIllegalArgumentException() throws Exception { + try { + TokenQueue tq = new TokenQueue("unbalanced(something(or another)) else"); + tq.consumeTo("("); + tq.chompBalanced('(', '+'); + org.junit.Assert.fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("Did not find balanced marker at \'something(or another)) else\'", expected.getMessage()); + } + } } From e499559601270be462bbcc665014dc061032134f Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Thu, 16 Aug 2018 10:55:27 -0700 Subject: [PATCH 313/774] maven-compiler-plugin 3.8.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 66298c2c75..91e76a41e0 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.8.0 1.7 1.7 From 7755a419b2db2f49132ad4ae83fc96bb9853c7d4 Mon Sep 17 00:00:00 2001 From: jhy Date: Wed, 15 May 2019 22:23:13 -0700 Subject: [PATCH 314/774] Don't recompile all on every build Maven 3 will always recompile the entire project if there are package-info.java documentation files, as they don't by default generate a .class for timestamp comparisons. https://issues.apache.org/jira/browse/MCOMPILER-205 --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 91e76a41e0..c66230606e 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,9 @@ 1.7 1.7 UTF-8 + + -Xpkginfo:always + From 7bd5cf5b1caf75db802121fc2e82507a7f3f5a10 Mon Sep 17 00:00:00 2001 From: jhy Date: Wed, 15 May 2019 22:59:44 -0700 Subject: [PATCH 315/774] Rollback -Xpkginfo:always OpenJDK 7 is throwing an NPE. Looks like a bug from 2013 that never got fixed, and no-one uses package-info.java for documentation. :( https://travis-ci.org/jhy/jsoup/jobs/533146999 https://bugs.openjdk.java.net/browse/JDK-8022161 --- pom.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pom.xml b/pom.xml index c66230606e..91e76a41e0 100644 --- a/pom.xml +++ b/pom.xml @@ -41,9 +41,6 @@ 1.7 1.7 UTF-8 - - -Xpkginfo:always - From d65510c8ed0f10561372838b1c15b9d8af658d8b Mon Sep 17 00:00:00 2001 From: offa Date: Tue, 14 May 2019 17:16:43 +0200 Subject: [PATCH 316/774] JDK 12 CI build added. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f303206a8b..bacde7d9ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ jdk: - oraclejdk9 - openjdk10 - oraclejdk11 + - openjdk12 cache: directories: From fd46489cd718ddea7b28f89953265e7ce4ec8372 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 16 May 2019 16:25:48 -0700 Subject: [PATCH 317/774] Moved some URL tests to local servlet --- .../org/jsoup/integration/ConnectTest.java | 33 ++++++++++++++++++- .../org/jsoup/integration/UrlConnectTest.java | 26 --------------- .../integration/servlets/EchoServlet.java | 12 +++++-- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index f2bb871986..976019730e 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -1,6 +1,7 @@ package org.jsoup.integration; import org.jsoup.Connection; +import org.jsoup.HttpStatusException; import org.jsoup.Jsoup; import org.jsoup.UncheckedIOException; import org.jsoup.integration.servlets.Deflateservlet; @@ -64,7 +65,7 @@ public void fetchURl() throws IOException { } @Test - public void fetchURIWithWihtespace() throws IOException { + public void fetchURIWithWhitespace() throws IOException { Connection con = Jsoup.connect(echoUrl + "#with whitespaces"); Document doc = con.get(); assertTrue(doc.title().contains("Environment Variables")); @@ -89,6 +90,36 @@ private static String ihVal(String key, Document doc) { return first != null ? first.text() : null; } + @Test + public void throwsExceptionOn404() { + String url = EchoServlet.Url; + Connection con = Jsoup.connect(url).header(EchoServlet.CodeParam, "404"); + + boolean threw = false; + try { + Document doc = con.get(); + } catch (HttpStatusException e) { + threw = true; + assertEquals("org.jsoup.HttpStatusException: HTTP error fetching URL. Status=404, URL=" + e.getUrl(), e.toString()); + assertTrue(e.getUrl().startsWith(url)); + assertEquals(404, e.getStatusCode()); + } catch (IOException e) { + } + assertTrue(threw); + } + + @Test + public void ignoresExceptionIfSoConfigured() throws IOException { + String url = EchoServlet.Url; + Connection con = Jsoup.connect(url) + .header(EchoServlet.CodeParam, "404") + .ignoreHttpErrors(true); + Connection.Response res = con.execute(); + Document doc = res.parse(); + assertEquals(404, res.statusCode()); + assertEquals("Webserver Environment Variables", doc.title()); + } + @Test public void doesPost() throws IOException { Document doc = Jsoup.connect(echoUrl) diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 8d9c91b606..1e69d89c74 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -146,32 +146,6 @@ public void gracefullyHandleBrokenLocationRedirect() throws IOException { assertTrue(true); } - @Test - public void throwsExceptionOnError() { - String url = "http://direct.infohound.net/tools/404"; - Connection con = Jsoup.connect(url); - boolean threw = false; - try { - Document doc = con.get(); - } catch (HttpStatusException e) { - threw = true; - assertEquals("org.jsoup.HttpStatusException: HTTP error fetching URL. Status=404, URL=http://direct.infohound.net/tools/404", e.toString()); - assertEquals(url, e.getUrl()); - assertEquals(404, e.getStatusCode()); - } catch (IOException e) { - } - assertTrue(threw); - } - - @Test - public void ignoresExceptionIfSoConfigured() throws IOException { - Connection con = Jsoup.connect("http://direct.infohound.net/tools/404").ignoreHttpErrors(true); - Connection.Response res = con.execute(); - Document doc = res.parse(); - assertEquals(404, res.statusCode()); - assertEquals("404 Not Found", doc.select("h1").first().text()); - } - @Test public void ignores500tExceptionIfSoConfigured() throws IOException { Connection con = Jsoup.connect("http://direct.infohound.net/tools/500.pl").ignoreHttpErrors(true); diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java index 3d357cc47c..e3eaebd2e2 100644 --- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -13,13 +13,16 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Enumeration; import static org.jsoup.nodes.Entities.escape; public class EchoServlet extends BaseServlet { + public static final String CodeParam = "code"; public static final String Url = TestServer.map(EchoServlet.class); + private static final int DefaultCode = HttpServletResponse.SC_OK; @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { @@ -37,10 +40,15 @@ protected void doPut(HttpServletRequest req, HttpServletResponse res) throws Ser } private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { + int intCode = DefaultCode; + String code = req.getHeader(CodeParam); + if (code != null) + intCode = Integer.parseInt(code); + boolean isMulti = maybeEnableMultipart(req); res.setContentType(TextHtml); - res.setStatus(HttpServletResponse.SC_OK); + res.setStatus(intCode); PrintWriter w = res.getWriter(); w.write("Webserver Environment Variables\n" + @@ -76,7 +84,7 @@ private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOExce // post body ByteBuffer byteBuffer = DataUtil.readToByteBuffer(req.getInputStream(), 0); - String postData = new String(byteBuffer.array(), "UTF-8"); + String postData = new String(byteBuffer.array(), StandardCharsets.UTF_8); if (!StringUtil.isBlank(postData)) { write(w, "Post Data", postData); } From 525d7c19ec44e5b4e462946f68746d6df74eb393 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 16 May 2019 16:32:58 -0700 Subject: [PATCH 318/774] Splelling --- src/main/java/org/jsoup/helper/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 00a1d98503..45840a890e 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -794,7 +794,7 @@ else if (methodHasBody) res.byteData = DataUtil.emptyByteBuffer(); } } catch (IOException e){ - // per Java's documentation, this is not necessary, and precludes keepalives. However in practise, + // per Java's documentation, this is not necessary, and precludes keepalives. However in practice, // connection errors will not be released quickly enough and can cause a too many open files error. conn.disconnect(); throw e; From 0fe6d51fd8b026e6a860376b228533da66654fb4 Mon Sep 17 00:00:00 2001 From: Zohar Kelrich Date: Thu, 16 May 2019 14:16:22 +0300 Subject: [PATCH 319/774] Fix :has selector to set the root, allowing relative selectors --- src/main/java/org/jsoup/select/StructuralEvaluator.java | 2 +- src/test/java/org/jsoup/select/SelectorTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index 8083d9e399..4ac28f2842 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -21,7 +21,7 @@ public Has(Evaluator evaluator) { public boolean matches(Element root, Element element) { for (Element e : element.getAllElements()) { - if (e != element && evaluator.matches(root, e)) + if (e != element && evaluator.matches(element, e)) return true; } return false; diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 41649b7f08..8dc5655902 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -504,6 +504,11 @@ public class SelectorTest { assertEquals("body", els1.first().tagName()); assertEquals("0", els1.get(1).id()); assertEquals("2", els1.get(2).id()); + + Elements els2 = doc.body().select(":has(> span)"); + assertEquals(2,els2.size()); // p, div + assertEquals("p",els2.first().tagName()); + assertEquals("1", els2.get(1).id()); } @Test public void testNestedHas() { From e4417540b2d24ee5218dbbf14df1f0f63002db02 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 16 May 2019 17:23:49 -0700 Subject: [PATCH 320/774] Changelog for #1214 And some styling to make the selector syntax easier to read --- CHANGES | 4 +++- src/main/java/org/jsoup/internal/StringUtil.java | 2 +- src/main/java/org/jsoup/select/Selector.java | 5 +++-- src/main/java/org/jsoup/select/package-info.java | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 1725c5f89f..46ad9244ce 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ jsoup changelog **** Release 1.12.2 [PENDING] - * ... + * Improvement: the :has() selector now supports relative selectors. For example, the query + "div:has(> a)" will select all "div" elements that have at least one direct child "a" element. + **** Release 1.12.1 [2019-May12] * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index cbdfc362d3..372e54580a 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -235,7 +235,7 @@ public static String resolve(final String baseUrl, final String relUrl) { * Maintains cached StringBuilders in a flyweight pattern, to minimize new StringBuilder GCs. The StringBuilder is * prevented from growing too large. *

    - * Care must be taken to release the builder once its work has been completed, with {@see #releaseBuilder} + * Care must be taken to release the builder once its work has been completed, with {@link #releaseBuilder} * @return an empty StringBuilder * @ */ diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 27f518e877..6c073c591f 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -19,7 +19,8 @@ * The universal selector (*) is implicit when no element selector is supplied (i.e. {@code *.header} and {@code .header} * is equivalent). *

    - * + * + *
    * * * @@ -46,7 +47,7 @@ * * * - * + * * * * diff --git a/src/main/java/org/jsoup/select/package-info.java b/src/main/java/org/jsoup/select/package-info.java index a6e6a2fa0f..e6871fc9e6 100644 --- a/src/main/java/org/jsoup/select/package-info.java +++ b/src/main/java/org/jsoup/select/package-info.java @@ -1,4 +1,5 @@ /** Packages to support the CSS-style element selector. + {@link org.jsoup.select.Selector Selector defines the query syntax.} */ -package org.jsoup.select; \ No newline at end of file +package org.jsoup.select; From a63ed9938a441c0496e7789b09e59d298370d9ae Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 16 May 2019 17:59:46 -0700 Subject: [PATCH 321/774] Limit style to the syntax table --- src/main/java/org/jsoup/select/Selector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 6c073c591f..61d7f2d0f0 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -19,8 +19,8 @@ * The universal selector (*) is implicit when no element selector is supplied (i.e. {@code *.header} and {@code .header} * is equivalent). *

    - * - *
    PatternMatchesExample
    *any element*
    tagelements with the given tag namediv
    :lt(n)elements whose sibling index is less than ntd:lt(3) finds the first 3 cells of each row
    :gt(n)elements whose sibling index is greater than ntd:gt(1) finds cells after skipping the first two
    :eq(n)elements whose sibling index is equal to ntd:eq(0) finds the first cell of each row
    :has(selector)elements that contains at least one element matching the selectordiv:has(p) finds divs that contain p elements
    :has(selector)elements that contains at least one element matching the selectordiv:has(p) finds divs that contain p elements.
    div:has(> a) selects div elements that have at least one direct child a element.
    :not(selector)elements that do not match the selector. See also {@link Elements#not(String)}div:not(.logo) finds all divs that do not have the "logo" class.

    div:not(:has(div)) finds divs that do not contain divs.

    :contains(text)elements that contains the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants.p:contains(jsoup) finds p elements containing the text "jsoup".
    :matches(regex)elements whose text matches the specified regular expression. The text may appear in the found element, or any of its descendants.td:matches(\\d+) finds table cells containing digits. div:matches((?i)login) finds divs containing the text, case insensitively.
    + * + *
    * * * From 1e576bbb7bb896b8f2ee7f3c20224d678fb21acf Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 17 May 2019 15:15:24 -0700 Subject: [PATCH 322/774] Added remaining typed Element chaining methods Fixes #1193 --- CHANGES | 3 + src/main/java/org/jsoup/nodes/Element.java | 27 ++++++ src/main/java/org/jsoup/nodes/Node.java | 2 +- .../java/org/jsoup/nodes/ElementTest.java | 83 +++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 46ad9244ce..1e77001205 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,9 @@ jsoup changelog "div:has(> a)" will select all "div" elements that have at least one direct child "a" element. + * Improvement: added Element chaining methods for various overridden methods on Node. + + **** Release 1.12.1 [2019-May12] * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 982aea6453..4a7403281f 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -8,6 +8,7 @@ import org.jsoup.select.Collector; import org.jsoup.select.Elements; import org.jsoup.select.Evaluator; +import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import org.jsoup.select.QueryParser; @@ -1482,6 +1483,32 @@ protected Element doClone(Node parent) { return clone; } + // overrides of Node for call chaining + @Override + public Element clearAttributes() { + return (Element) super.clearAttributes(); + } + + @Override + public Element removeAttr(String attributeKey) { + return (Element) super.removeAttr(attributeKey); + } + + @Override + public Element root() { + return (Element) super.root(); // probably a document, but always at least an element + } + + @Override + public Element traverse(NodeVisitor nodeVisitor) { + return (Element) super.traverse(nodeVisitor); + } + + @Override + public Element filter(NodeFilter nodeFilter) { + return (Element) super.filter(nodeFilter); + } + private static final class NodeList extends ChangeNotifyingArrayList { private final Element owner; diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index eb64669cb3..fbe47d6cfa 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -104,7 +104,7 @@ public boolean hasAttr(String attributeKey) { } /** - * Remove an attribute from this element. + * Remove an attribute from this node. * @param attributeKey The attribute to remove. * @return this (for chaining) */ diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 210a8834fe..a41653577b 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -4,6 +4,8 @@ import org.jsoup.TextUtil; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; +import org.jsoup.select.NodeFilter; +import org.jsoup.select.NodeVisitor; import org.junit.Test; import java.util.ArrayList; @@ -13,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; @@ -1399,4 +1402,84 @@ public void testPreviousElementSiblings() { List elementSiblings3 = ul.previousElementSiblings(); assertEquals(0, elementSiblings3.size()); } + + @Test + public void testClearAttributes() { + Element el = new Element("a").attr("href", "http://example.com").text("Hello"); + assertEquals("Hello", el.outerHtml()); + Element el2 = el.clearAttributes(); // really just force testing the return type is Element + assertSame(el, el2); + assertEquals("Hello", el2.outerHtml()); + } + + @Test + public void testRemoveAttr() { + Element el = new Element("a") + .attr("href", "http://example.com") + .attr("id", "1") + .text("Hello"); + assertEquals("Hello", el.outerHtml()); + Element el2 = el.removeAttr("href"); // really just force testing the return type is Element + assertSame(el, el2); + assertEquals("Hello", el2.outerHtml()); + } + + @Test + public void testRoot() { + Element el = new Element("a"); + el.append("Hello"); + assertEquals("Hello", el.outerHtml()); + Element span = el.selectFirst("span"); + assertNotNull(span); + Element el2 = span.root(); + assertSame(el, el2); + + Document doc = Jsoup.parse("

    One

    Two

    Three"); + Element div = doc.selectFirst("div"); + assertSame(doc, div.root()); + assertSame(doc, div.ownerDocument()); + } + + @Test + public void testTraverse() { + Document doc = Jsoup.parse("

    One

    Two

    Three"); + Element div = doc.selectFirst("div"); + final AtomicInteger counter = new AtomicInteger(0); + + Element div2 = div.traverse(new NodeVisitor() { + + @Override + public void head(Node node, int depth) { + counter.incrementAndGet(); + } + + @Override + public void tail(Node node, int depth) { + + } + }); + + assertEquals(7, counter.get()); + assertEquals(div2, div); + } + + @Test + public void voidTestFilterCallReturnsElement() { + // doesn't actually test the filter so much as the return type for Element. See node.nodeFilter for an acutal test + Document doc = Jsoup.parse("

    One

    Two

    Three"); + Element div = doc.selectFirst("div"); + Element div2 = div.filter(new NodeFilter() { + @Override + public FilterResult head(Node node, int depth) { + return FilterResult.CONTINUE; + } + + @Override + public FilterResult tail(Node node, int depth) { + return FilterResult.CONTINUE; + } + }); + + assertSame(div, div2); + } } From 9e36f9e26cb8342ea54ad1379d6b1bfee718e90e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 17 May 2019 15:58:00 -0700 Subject: [PATCH 323/774] A few cleanups / sanity assertions --- src/main/java/org/jsoup/examples/HtmlToPlainText.java | 4 ++-- src/main/java/org/jsoup/helper/DataUtil.java | 6 ++++-- src/main/java/org/jsoup/helper/HttpConnection.java | 4 +++- src/main/java/org/jsoup/parser/CharacterReader.java | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/examples/HtmlToPlainText.java b/src/main/java/org/jsoup/examples/HtmlToPlainText.java index 8a00e79ada..c83219c7f2 100644 --- a/src/main/java/org/jsoup/examples/HtmlToPlainText.java +++ b/src/main/java/org/jsoup/examples/HtmlToPlainText.java @@ -66,7 +66,7 @@ public String getPlainText(Element element) { } // the formatting rules, implemented in a breadth-first DOM traverse - private class FormattingVisitor implements NodeVisitor { + private static class FormattingVisitor implements NodeVisitor { private static final int maxWidth = 80; private int width = 0; private StringBuilder accum = new StringBuilder(); // holds the accumulated text @@ -102,7 +102,7 @@ private void append(String text) { return; // don't accumulate long runs of empty spaces if (text.length() + width > maxWidth) { // won't fit, needs to wrap - String words[] = text.split("\\s+"); + String[] words = text.split("\\s+"); for (int i = 0; i < words.length; i++) { String word = words[i]; boolean last = i == words.length - 1; diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index e921e20b0d..49b7f09b17 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -163,8 +163,10 @@ else if (first instanceof Comment) { if (charsetName == null) charsetName = defaultCharset; BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); - if (bomCharset != null && bomCharset.offset) // creating the buffered reader ignores the input pos, so must skip here - reader.skip(1); + if (bomCharset != null && bomCharset.offset) { // creating the buffered reader ignores the input pos, so must skip here + long skipped = reader.skip(1); + Validate.isTrue(skipped == 1); // WTF if this fails. + } try { doc = parser.parseInput(reader, baseUri); } catch (UncheckedIOException e) { diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 45840a890e..f834b1a6c4 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -25,6 +25,7 @@ import java.net.MalformedURLException; import java.net.Proxy; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.nio.Buffer; @@ -102,7 +103,8 @@ static URL encodeUrl(URL u) { urlS = urlS.replaceAll(" ", "%20"); final URI uri = new URI(urlS); return new URL(uri.toASCIIString()); - } catch (Exception e) { + } catch (URISyntaxException | MalformedURLException e) { + // give up and return the original input return u; } } diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 7f163dd15e..01fd4220a6 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -53,11 +53,12 @@ private void bufferUp() { return; try { - reader.skip(pos); + final long skipped = reader.skip(pos); reader.mark(maxBufferLen); final int read = reader.read(charBuf); reader.reset(); if (read != -1) { + Validate.isTrue(skipped == pos); // Previously asserted that there is room in buf to skip, so this will be a WTF bufLength = read; readerPos += pos; bufPos = 0; From a47a7e45dd22d293e4b1f4fd7fdf8b2d83e539be Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 17 May 2019 16:13:57 -0700 Subject: [PATCH 324/774] Allocate entities in static section; saves 16K from .jar Identified via FindBugs --- src/main/java/org/jsoup/nodes/EntitiesData.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/EntitiesData.java b/src/main/java/org/jsoup/nodes/EntitiesData.java index 6a04e0a4f1..27cd78ee2f 100644 --- a/src/main/java/org/jsoup/nodes/EntitiesData.java +++ b/src/main/java/org/jsoup/nodes/EntitiesData.java @@ -4,7 +4,15 @@ * Holds packed data that represents Entity name=value pairs. Parsed by Entities, created by BuildEntities. */ class EntitiesData { - static final String xmlPoints = "amp=12;1>=1q;3<=1o;2"=y;0&"; - static final String basePoints = "AElig=5i;1c&=12;2Á=5d;17Â=5e;18À=5c;16Å=5h;1bÃ=5f;19Ä=5g;1a©=4p;hÇ=5j;1dÐ=5s;1mÉ=5l;1fÊ=5m;1gÈ=5k;1eË=5n;1h>=1q;6Í=5p;1jÎ=5q;1kÌ=5o;1iÏ=5r;1l<=1o;4Ñ=5t;1nÓ=5v;1pÔ=5w;1qÒ=5u;1oØ=60;1uÕ=5x;1rÖ=5y;1s"=y;0®=4u;nÞ=66;20Ú=62;1wÛ=63;1xÙ=61;1vÜ=64;1yÝ=65;1zá=69;23â=6a;24´=50;uæ=6e;28à=68;22&=12;3å=6d;27ã=6b;25ä=6c;26¦=4m;eç=6f;29¸=54;y¢=4i;a©=4p;i¤=4k;c°=4w;q÷=6v;2pé=6h;2bê=6i;2cè=6g;2að=6o;2ië=6j;2d½=59;13¼=58;12¾=5a;14>=1q;7í=6l;2fî=6m;2g¡=4h;9ì=6k;2e¿=5b;15ï=6n;2h«=4r;k<=1o;5¯=4v;pµ=51;v·=53;x =4g;8¬=4s;lñ=6p;2jó=6r;2lô=6s;2mò=6q;2kª=4q;jº=56;10ø=6w;2qõ=6t;2nö=6u;2o¶=52;w±=4x;r£=4j;b"=y;1»=57;11®=4u;o§=4n;f­=4t;m¹=55;z²=4y;s³=4z;tß=67;21þ=72;2w×=5z;1tú=6y;2sû=6z;2tù=6x;2r¨=4o;gü=70;2uý=71;2v¥=4l;dÿ=73;2x&"; - static final String fullPoints = "AElig=5i;2v&=12;8Á=5d;2p&Abreve=76;4kÂ=5e;2q&Acy=sw;av&Afr=2kn8;1khÀ=5c;2o&Alpha=pd;8d&Amacr=74;4i&And=8cz;1e1&Aogon=78;4m&Aopf=2koo;1ls&ApplyFunction=6e9;ewÅ=5h;2t&Ascr=2kkc;1jc&Assign=6s4;s6Ã=5f;2rÄ=5g;2s&Backslash=6qe;o1&Barv=8h3;1it&Barwed=6x2;120&Bcy=sx;aw&Because=6r9;pw&Bernoullis=6jw;gn&Beta=pe;8e&Bfr=2kn9;1ki&Bopf=2kop;1lt&Breve=k8;82&Bscr=6jw;gp&Bumpeq=6ry;ro&CHcy=tj;bi©=4p;1q&Cacute=7a;4o&Cap=6vm;zz&CapitalDifferentialD=6kl;h8&Cayleys=6jx;gq&Ccaron=7g;4uÇ=5j;2w&Ccirc=7c;4q&Cconint=6r4;pn&Cdot=7e;4s&Cedilla=54;2e&CenterDot=53;2b&Cfr=6jx;gr&Chi=pz;8y&CircleDot=6u1;x8&CircleMinus=6ty;x3&CirclePlus=6tx;x1&CircleTimes=6tz;x5&ClockwiseContourIntegral=6r6;pp&CloseCurlyDoubleQuote=6cd;e0&CloseCurlyQuote=6c9;dt&Colon=6rb;q1&Colone=8dw;1en&Congruent=6sh;sn&Conint=6r3;pm&ContourIntegral=6r2;pi&Copf=6iq;f7&Coproduct=6q8;nq&CounterClockwiseContourIntegral=6r7;pr&Cross=8bz;1d8&Cscr=2kke;1jd&Cup=6vn;100&CupCap=6rx;rk&DD=6kl;h9&DDotrahd=841;184&DJcy=si;ai&DScy=sl;al&DZcy=sv;au&Dagger=6ch;e7&Darr=6n5;j5&Dashv=8h0;1ir&Dcaron=7i;4w&Dcy=t0;az&Del=6pz;n9&Delta=pg;8g&Dfr=2knb;1kj&DiacriticalAcute=50;27&DiacriticalDot=k9;84&DiacriticalDoubleAcute=kd;8a&DiacriticalGrave=2o;13&DiacriticalTilde=kc;88&Diamond=6v8;za&DifferentialD=6km;ha&Dopf=2kor;1lu&Dot=4o;1n&DotDot=6ho;f5&DotEqual=6s0;rw&DoubleContourIntegral=6r3;pl&DoubleDot=4o;1m&DoubleDownArrow=6oj;m0&DoubleLeftArrow=6og;lq&DoubleLeftRightArrow=6ok;m3&DoubleLeftTee=8h0;1iq&DoubleLongLeftArrow=7w8;17g&DoubleLongLeftRightArrow=7wa;17m&DoubleLongRightArrow=7w9;17j&DoubleRightArrow=6oi;lw&DoubleRightTee=6ug;xz&DoubleUpArrow=6oh;lt&DoubleUpDownArrow=6ol;m7&DoubleVerticalBar=6qt;ov&DownArrow=6mr;i8&DownArrowBar=843;186&DownArrowUpArrow=6ph;mn&DownBreve=lt;8c&DownLeftRightVector=85s;198&DownLeftTeeVector=866;19m&DownLeftVector=6nx;ke&DownLeftVectorBar=85y;19e&DownRightTeeVector=867;19n&DownRightVector=6o1;kq&DownRightVectorBar=85z;19f&DownTee=6uc;xs&DownTeeArrow=6nb;jh&Downarrow=6oj;m1&Dscr=2kkf;1je&Dstrok=7k;4y&ENG=96;6gÐ=5s;35É=5l;2y&Ecaron=7u;56Ê=5m;2z&Ecy=tp;bo&Edot=7q;52&Efr=2knc;1kkÈ=5k;2x&Element=6q0;na&Emacr=7m;50&EmptySmallSquare=7i3;15x&EmptyVerySmallSquare=7fv;150&Eogon=7s;54&Eopf=2kos;1lv&Epsilon=ph;8h&Equal=8dx;1eo&EqualTilde=6rm;qp&Equilibrium=6oc;li&Escr=6k0;gu&Esim=8dv;1em&Eta=pj;8jË=5n;30&Exists=6pv;mz&ExponentialE=6kn;hc&Fcy=tg;bf&Ffr=2knd;1kl&FilledSmallSquare=7i4;15y&FilledVerySmallSquare=7fu;14w&Fopf=2kot;1lw&ForAll=6ps;ms&Fouriertrf=6k1;gv&Fscr=6k1;gw&GJcy=sj;aj>=1q;r&Gamma=pf;8f&Gammad=rg;a5&Gbreve=7y;5a&Gcedil=82;5e&Gcirc=7w;58&Gcy=sz;ay&Gdot=80;5c&Gfr=2kne;1km&Gg=6vt;10c&Gopf=2kou;1lx&GreaterEqual=6sl;sv&GreaterEqualLess=6vv;10i&GreaterFullEqual=6sn;t6&GreaterGreater=8f6;1gh&GreaterLess=6t3;ul&GreaterSlantEqual=8e6;1f5&GreaterTilde=6sz;ub&Gscr=2kki;1jf&Gt=6sr;tr&HARDcy=tm;bl&Hacek=jr;80&Hat=2m;10&Hcirc=84;5f&Hfr=6j0;fe&HilbertSpace=6iz;fa&Hopf=6j1;fg&HorizontalLine=7b4;13i&Hscr=6iz;fc&Hstrok=86;5h&HumpDownHump=6ry;rn&HumpEqual=6rz;rs&IEcy=t1;b0&IJlig=8i;5s&IOcy=sh;ahÍ=5p;32Î=5q;33&Icy=t4;b3&Idot=8g;5p&Ifr=6j5;fqÌ=5o;31&Im=6j5;fr&Imacr=8a;5l&ImaginaryI=6ko;hf&Implies=6oi;ly&Int=6r0;pf&Integral=6qz;pd&Intersection=6v6;z4&InvisibleComma=6eb;f0&InvisibleTimes=6ea;ey&Iogon=8e;5n&Iopf=2kow;1ly&Iota=pl;8l&Iscr=6j4;fn&Itilde=88;5j&Iukcy=sm;amÏ=5r;34&Jcirc=8k;5u&Jcy=t5;b4&Jfr=2knh;1kn&Jopf=2kox;1lz&Jscr=2kkl;1jg&Jsercy=so;ao&Jukcy=sk;ak&KHcy=th;bg&KJcy=ss;as&Kappa=pm;8m&Kcedil=8m;5w&Kcy=t6;b5&Kfr=2kni;1ko&Kopf=2koy;1m0&Kscr=2kkm;1jh&LJcy=sp;ap<=1o;m&Lacute=8p;5z&Lambda=pn;8n&Lang=7vu;173&Laplacetrf=6j6;fs&Larr=6n2;j1&Lcaron=8t;63&Lcedil=8r;61&Lcy=t7;b6&LeftAngleBracket=7vs;16x&LeftArrow=6mo;hu&LeftArrowBar=6p0;mj&LeftArrowRightArrow=6o6;l3&LeftCeiling=6x4;121&LeftDoubleBracket=7vq;16t&LeftDownTeeVector=869;19p&LeftDownVector=6o3;kw&LeftDownVectorBar=861;19h&LeftFloor=6x6;125&LeftRightArrow=6ms;ib&LeftRightVector=85q;196&LeftTee=6ub;xq&LeftTeeArrow=6n8;ja&LeftTeeVector=862;19i&LeftTriangle=6uq;ya&LeftTriangleBar=89b;1c0&LeftTriangleEqual=6us;yg&LeftUpDownVector=85t;199&LeftUpTeeVector=868;19o&LeftUpVector=6nz;kk&LeftUpVectorBar=860;19g&LeftVector=6nw;kb&LeftVectorBar=85u;19a&Leftarrow=6og;lr&Leftrightarrow=6ok;m4&LessEqualGreater=6vu;10e&LessFullEqual=6sm;t0&LessGreater=6t2;ui&LessLess=8f5;1gf&LessSlantEqual=8e5;1ez&LessTilde=6sy;u8&Lfr=2knj;1kp&Ll=6vs;109&Lleftarrow=6oq;me&Lmidot=8v;65&LongLeftArrow=7w5;177&LongLeftRightArrow=7w7;17d&LongRightArrow=7w6;17a&Longleftarrow=7w8;17h&Longleftrightarrow=7wa;17n&Longrightarrow=7w9;17k&Lopf=2koz;1m1&LowerLeftArrow=6mx;iq&LowerRightArrow=6mw;in&Lscr=6j6;fu&Lsh=6nk;jv&Lstrok=8x;67&Lt=6sq;tl&Map=83p;17v&Mcy=t8;b7&MediumSpace=6e7;eu&Mellintrf=6k3;gx&Mfr=2knk;1kq&MinusPlus=6qb;nv&Mopf=2kp0;1m2&Mscr=6k3;gz&Mu=po;8o&NJcy=sq;aq&Nacute=8z;69&Ncaron=93;6d&Ncedil=91;6b&Ncy=t9;b8&NegativeMediumSpace=6bv;dc&NegativeThickSpace=6bv;dd&NegativeThinSpace=6bv;de&NegativeVeryThinSpace=6bv;db&NestedGreaterGreater=6sr;tq&NestedLessLess=6sq;tk&NewLine=a;1&Nfr=2knl;1kr&NoBreak=6e8;ev&NonBreakingSpace=4g;1d&Nopf=6j9;fx&Not=8h8;1ix&NotCongruent=6si;sp&NotCupCap=6st;tv&NotDoubleVerticalBar=6qu;p0&NotElement=6q1;ne&NotEqual=6sg;sk&NotEqualTilde=6rm,mw;qn&NotExists=6pw;n1&NotGreater=6sv;tz&NotGreaterEqual=6sx;u5&NotGreaterFullEqual=6sn,mw;t3&NotGreaterGreater=6sr,mw;tn&NotGreaterLess=6t5;uq&NotGreaterSlantEqual=8e6,mw;1f2&NotGreaterTilde=6t1;ug&NotHumpDownHump=6ry,mw;rl&NotHumpEqual=6rz,mw;rq&NotLeftTriangle=6wa;113&NotLeftTriangleBar=89b,mw;1bz&NotLeftTriangleEqual=6wc;119&NotLess=6su;tw&NotLessEqual=6sw;u2&NotLessGreater=6t4;uo&NotLessLess=6sq,mw;th&NotLessSlantEqual=8e5,mw;1ew&NotLessTilde=6t0;ue&NotNestedGreaterGreater=8f6,mw;1gg&NotNestedLessLess=8f5,mw;1ge&NotPrecedes=6tc;vb&NotPrecedesEqual=8fj,mw;1gv&NotPrecedesSlantEqual=6w0;10p&NotReverseElement=6q4;nl&NotRightTriangle=6wb;116&NotRightTriangleBar=89c,mw;1c1&NotRightTriangleEqual=6wd;11c&NotSquareSubset=6tr,mw;wh&NotSquareSubsetEqual=6w2;10t&NotSquareSuperset=6ts,mw;wl&NotSquareSupersetEqual=6w3;10v&NotSubset=6te,6he;vh&NotSubsetEqual=6tk;w0&NotSucceeds=6td;ve&NotSucceedsEqual=8fk,mw;1h1&NotSucceedsSlantEqual=6w1;10r&NotSucceedsTilde=6tb,mw;v7&NotSuperset=6tf,6he;vm&NotSupersetEqual=6tl;w3&NotTilde=6rl;ql&NotTildeEqual=6ro;qv&NotTildeFullEqual=6rr;r1&NotTildeTilde=6rt;r9&NotVerticalBar=6qs;or&Nscr=2kkp;1jiÑ=5t;36&Nu=pp;8p&OElig=9e;6mÓ=5v;38Ô=5w;39&Ocy=ta;b9&Odblac=9c;6k&Ofr=2knm;1ksÒ=5u;37&Omacr=98;6i&Omega=q1;90&Omicron=pr;8r&Oopf=2kp2;1m3&OpenCurlyDoubleQuote=6cc;dy&OpenCurlyQuote=6c8;dr&Or=8d0;1e2&Oscr=2kkq;1jjØ=60;3dÕ=5x;3a&Otimes=8c7;1dfÖ=5y;3b&OverBar=6da;em&OverBrace=732;13b&OverBracket=71w;134&OverParenthesis=730;139&PartialD=6pu;mx&Pcy=tb;ba&Pfr=2knn;1kt&Phi=py;8x&Pi=ps;8s&PlusMinus=4x;22&Poincareplane=6j0;fd&Popf=6jd;g3&Pr=8fv;1hl&Precedes=6t6;us&PrecedesEqual=8fj;1gy&PrecedesSlantEqual=6t8;uy&PrecedesTilde=6ta;v4&Prime=6cz;eg&Product=6q7;no&Proportion=6rb;q0&Proportional=6ql;oa&Pscr=2kkr;1jk&Psi=q0;8z"=y;3&Qfr=2kno;1ku&Qopf=6je;g5&Qscr=2kks;1jl&RBarr=840;183®=4u;1x&Racute=9g;6o&Rang=7vv;174&Rarr=6n4;j4&Rarrtl=846;187&Rcaron=9k;6s&Rcedil=9i;6q&Rcy=tc;bb&Re=6jg;gb&ReverseElement=6q3;nh&ReverseEquilibrium=6ob;le&ReverseUpEquilibrium=86n;1a4&Rfr=6jg;ga&Rho=pt;8t&RightAngleBracket=7vt;170&RightArrow=6mq;i3&RightArrowBar=6p1;ml&RightArrowLeftArrow=6o4;ky&RightCeiling=6x5;123&RightDoubleBracket=7vr;16v&RightDownTeeVector=865;19l&RightDownVector=6o2;kt&RightDownVectorBar=85x;19d&RightFloor=6x7;127&RightTee=6ua;xo&RightTeeArrow=6na;je&RightTeeVector=863;19j&RightTriangle=6ur;yd&RightTriangleBar=89c;1c2&RightTriangleEqual=6ut;yk&RightUpDownVector=85r;197&RightUpTeeVector=864;19k&RightUpVector=6ny;kh&RightUpVectorBar=85w;19c&RightVector=6o0;kn&RightVectorBar=85v;19b&Rightarrow=6oi;lx&Ropf=6jh;gd&RoundImplies=86o;1a6&Rrightarrow=6or;mg&Rscr=6jf;g7&Rsh=6nl;jx&RuleDelayed=8ac;1cb&SHCHcy=tl;bk&SHcy=tk;bj&SOFTcy=to;bn&Sacute=9m;6u&Sc=8fw;1hm&Scaron=9s;70&Scedil=9q;6y&Scirc=9o;6w&Scy=td;bc&Sfr=2knq;1kv&ShortDownArrow=6mr;i7&ShortLeftArrow=6mo;ht&ShortRightArrow=6mq;i2&ShortUpArrow=6mp;hy&Sigma=pv;8u&SmallCircle=6qg;o6&Sopf=2kp6;1m4&Sqrt=6qi;o9&Square=7fl;14t&SquareIntersection=6tv;ww&SquareSubset=6tr;wi&SquareSubsetEqual=6tt;wp&SquareSuperset=6ts;wm&SquareSupersetEqual=6tu;ws&SquareUnion=6tw;wz&Sscr=2kku;1jm&Star=6va;zf&Sub=6vk;zw&Subset=6vk;zv&SubsetEqual=6ti;vu&Succeeds=6t7;uv&SucceedsEqual=8fk;1h4&SucceedsSlantEqual=6t9;v1&SucceedsTilde=6tb;v8&SuchThat=6q3;ni&Sum=6q9;ns&Sup=6vl;zy&Superset=6tf;vp&SupersetEqual=6tj;vx&Supset=6vl;zxÞ=66;3j&TRADE=6jm;gf&TSHcy=sr;ar&TScy=ti;bh&Tab=9;0&Tau=pw;8v&Tcaron=9w;74&Tcedil=9u;72&Tcy=te;bd&Tfr=2knr;1kw&Therefore=6r8;pt&Theta=pk;8k&ThickSpace=6e7,6bu;et&ThinSpace=6bt;d7&Tilde=6rg;q9&TildeEqual=6rn;qs&TildeFullEqual=6rp;qy&TildeTilde=6rs;r4&Topf=2kp7;1m5&TripleDot=6hn;f3&Tscr=2kkv;1jn&Tstrok=9y;76Ú=62;3f&Uarr=6n3;j2&Uarrocir=85l;193&Ubrcy=su;at&Ubreve=a4;7cÛ=63;3g&Ucy=tf;be&Udblac=a8;7g&Ufr=2kns;1kxÙ=61;3e&Umacr=a2;7a&UnderBar=2n;11&UnderBrace=733;13c&UnderBracket=71x;136&UnderParenthesis=731;13a&Union=6v7;z8&UnionPlus=6tq;wf&Uogon=aa;7i&Uopf=2kp8;1m6&UpArrow=6mp;hz&UpArrowBar=842;185&UpArrowDownArrow=6o5;l1&UpDownArrow=6mt;ie&UpEquilibrium=86m;1a2&UpTee=6ud;xv&UpTeeArrow=6n9;jc&Uparrow=6oh;lu&Updownarrow=6ol;m8&UpperLeftArrow=6mu;ih&UpperRightArrow=6mv;ik&Upsi=r6;9z&Upsilon=px;8w&Uring=a6;7e&Uscr=2kkw;1jo&Utilde=a0;78Ü=64;3h&VDash=6uj;y3&Vbar=8h7;1iw&Vcy=sy;ax&Vdash=6uh;y1&Vdashl=8h2;1is&Vee=6v5;z3&Verbar=6c6;dp&Vert=6c6;dq&VerticalBar=6qr;on&VerticalLine=3g;18&VerticalSeparator=7rs;16o&VerticalTilde=6rk;qi&VeryThinSpace=6bu;d9&Vfr=2knt;1ky&Vopf=2kp9;1m7&Vscr=2kkx;1jp&Vvdash=6ui;y2&Wcirc=ac;7k&Wedge=6v4;z0&Wfr=2knu;1kz&Wopf=2kpa;1m8&Wscr=2kky;1jq&Xfr=2knv;1l0&Xi=pq;8q&Xopf=2kpb;1m9&Xscr=2kkz;1jr&YAcy=tr;bq&YIcy=sn;an&YUcy=tq;bpÝ=65;3i&Ycirc=ae;7m&Ycy=tn;bm&Yfr=2knw;1l1&Yopf=2kpc;1ma&Yscr=2kl0;1js&Yuml=ag;7o&ZHcy=t2;b1&Zacute=ah;7p&Zcaron=al;7t&Zcy=t3;b2&Zdot=aj;7r&ZeroWidthSpace=6bv;df&Zeta=pi;8i&Zfr=6js;gl&Zopf=6jo;gi&Zscr=2kl1;1jtá=69;3m&abreve=77;4l&ac=6ri;qg&acE=6ri,mr;qe&acd=6rj;qhâ=6a;3n´=50;28&acy=ts;bræ=6e;3r&af=6e9;ex&afr=2kny;1l2à=68;3l&alefsym=6k5;h3&aleph=6k5;h4&alpha=q9;92&amacr=75;4j&amalg=8cf;1dm&=12;9&and=6qv;p6&andand=8d1;1e3&andd=8d8;1e9&andslope=8d4;1e6&andv=8d6;1e7&ang=6qo;oj&ange=884;1b1&angle=6qo;oi&angmsd=6qp;ol&angmsdaa=888;1b5&angmsdab=889;1b6&angmsdac=88a;1b7&angmsdad=88b;1b8&angmsdae=88c;1b9&angmsdaf=88d;1ba&angmsdag=88e;1bb&angmsdah=88f;1bc&angrt=6qn;og&angrtvb=6v2;yw&angrtvbd=87x;1b0&angsph=6qq;om&angst=5h;2u&angzarr=70c;12z&aogon=79;4n&aopf=2kpe;1mb&ap=6rs;r8&apE=8ds;1ej&apacir=8dr;1eh&ape=6ru;rd&apid=6rv;rf&apos=13;a&approx=6rs;r5&approxeq=6ru;rcå=6d;3q&ascr=2kl2;1ju&ast=16;e&asymp=6rs;r6&asympeq=6rx;rjã=6b;3oä=6c;3p&awconint=6r7;ps&awint=8b5;1cr&bNot=8h9;1iy&backcong=6rw;rg&backepsilon=s6;af&backprime=6d1;ei&backsim=6rh;qc&backsimeq=6vh;zp&barvee=6v1;yv&barwed=6x1;11y&barwedge=6x1;11x&bbrk=71x;137&bbrktbrk=71y;138&bcong=6rw;rh&bcy=tt;bs&bdquo=6ce;e4&becaus=6r9;py&because=6r9;px&bemptyv=88g;1bd&bepsi=s6;ag&bernou=6jw;go&beta=qa;93&beth=6k6;h5&between=6ss;tt&bfr=2knz;1l3&bigcap=6v6;z5&bigcirc=7hr;15s&bigcup=6v7;z7&bigodot=8ao;1cd&bigoplus=8ap;1cf&bigotimes=8aq;1ch&bigsqcup=8au;1cl&bigstar=7id;15z&bigtriangledown=7gd;15e&bigtriangleup=7g3;154&biguplus=8as;1cj&bigvee=6v5;z1&bigwedge=6v4;yy&bkarow=83x;17x&blacklozenge=8a3;1c9&blacksquare=7fu;14x&blacktriangle=7g4;156&blacktriangledown=7ge;15g&blacktriangleleft=7gi;15k&blacktriangleright=7g8;15a&blank=74z;13f&blk12=7f6;14r&blk14=7f5;14q&blk34=7f7;14s&block=7ew;14p&bne=1p,6hx;o&bnequiv=6sh,6hx;sm&bnot=6xc;12d&bopf=2kpf;1mc&bot=6ud;xx&bottom=6ud;xu&bowtie=6vc;zi&boxDL=7dj;141&boxDR=7dg;13y&boxDl=7di;140&boxDr=7df;13x&boxH=7dc;13u&boxHD=7dy;14g&boxHU=7e1;14j&boxHd=7dw;14e&boxHu=7dz;14h&boxUL=7dp;147&boxUR=7dm;144&boxUl=7do;146&boxUr=7dl;143&boxV=7dd;13v&boxVH=7e4;14m&boxVL=7dv;14d&boxVR=7ds;14a&boxVh=7e3;14l&boxVl=7du;14c&boxVr=7dr;149&boxbox=895;1bw&boxdL=7dh;13z&boxdR=7de;13w&boxdl=7bk;13m&boxdr=7bg;13l&boxh=7b4;13j&boxhD=7dx;14f&boxhU=7e0;14i&boxhd=7cc;13r&boxhu=7ck;13s&boxminus=6u7;xi&boxplus=6u6;xg&boxtimes=6u8;xk&boxuL=7dn;145&boxuR=7dk;142&boxul=7bs;13o&boxur=7bo;13n&boxv=7b6;13k&boxvH=7e2;14k&boxvL=7dt;14b&boxvR=7dq;148&boxvh=7cs;13t&boxvl=7c4;13q&boxvr=7bw;13p&bprime=6d1;ej&breve=k8;83¦=4m;1k&bscr=2kl3;1jv&bsemi=6dr;er&bsim=6rh;qd&bsime=6vh;zq&bsol=2k;x&bsolb=891;1bv&bsolhsub=7uw;16r&bull=6ci;e9&bullet=6ci;e8&bump=6ry;rp&bumpE=8fi;1gu&bumpe=6rz;ru&bumpeq=6rz;rt&cacute=7b;4p&cap=6qx;pa&capand=8ck;1dq&capbrcup=8cp;1dv&capcap=8cr;1dx&capcup=8cn;1dt&capdot=8cg;1dn&caps=6qx,1e68;p9&caret=6dd;eo&caron=jr;81&ccaps=8ct;1dz&ccaron=7h;4vç=6f;3s&ccirc=7d;4r&ccups=8cs;1dy&ccupssm=8cw;1e0&cdot=7f;4t¸=54;2f&cemptyv=88i;1bf¢=4i;1g¢erdot=53;2c&cfr=2ko0;1l4&chcy=uf;ce&check=7pv;16j&checkmark=7pv;16i&chi=qv;9s&cir=7gr;15q&cirE=88z;1bt&circ=jq;7z&circeq=6s7;sc&circlearrowleft=6nu;k6&circlearrowright=6nv;k8&circledR=4u;1w&circledS=79k;13g&circledast=6u3;xc&circledcirc=6u2;xa&circleddash=6u5;xe&cire=6s7;sd&cirfnint=8b4;1cq&cirmid=8hb;1j0&cirscir=88y;1bs&clubs=7kz;168&clubsuit=7kz;167&colon=1m;j&colone=6s4;s7&coloneq=6s4;s5&comma=18;g&commat=1s;u&comp=6pt;mv&compfn=6qg;o7&complement=6pt;mu&complexes=6iq;f6&cong=6rp;qz&congdot=8dp;1ef&conint=6r2;pj&copf=2kpg;1md&coprod=6q8;nr©=4p;1r©sr=6jb;fz&crarr=6np;k1&cross=7pz;16k&cscr=2kl4;1jw&csub=8gf;1id&csube=8gh;1if&csup=8gg;1ie&csupe=8gi;1ig&ctdot=6wf;11g&cudarrl=854;18x&cudarrr=851;18u&cuepr=6vy;10m&cuesc=6vz;10o&cularr=6nq;k3&cularrp=859;190&cup=6qy;pc&cupbrcap=8co;1du&cupcap=8cm;1ds&cupcup=8cq;1dw&cupdot=6tp;we&cupor=8cl;1dr&cups=6qy,1e68;pb&curarr=6nr;k5&curarrm=858;18z&curlyeqprec=6vy;10l&curlyeqsucc=6vz;10n&curlyvee=6vi;zr&curlywedge=6vj;zt¤=4k;1i&curvearrowleft=6nq;k2&curvearrowright=6nr;k4&cuvee=6vi;zs&cuwed=6vj;zu&cwconint=6r6;pq&cwint=6r5;po&cylcty=6y5;12u&dArr=6oj;m2&dHar=86d;19t&dagger=6cg;e5&daleth=6k8;h7&darr=6mr;ia&dash=6c0;dl&dashv=6ub;xr&dbkarow=83z;180&dblac=kd;8b&dcaron=7j;4x&dcy=tw;bv&dd=6km;hb&ddagger=6ch;e6&ddarr=6oa;ld&ddotseq=8dz;1ep°=4w;21&delta=qc;95&demptyv=88h;1be&dfisht=873;1aj&dfr=2ko1;1l5&dharl=6o3;kx&dharr=6o2;ku&diam=6v8;zc&diamond=6v8;zb&diamondsuit=7l2;16b&diams=7l2;16c&die=4o;1o&digamma=rh;a6&disin=6wi;11j&div=6v;49÷=6v;48÷ontimes=6vb;zg&divonx=6vb;zh&djcy=uq;co&dlcorn=6xq;12n&dlcrop=6x9;12a&dollar=10;6&dopf=2kph;1me&dot=k9;85&doteq=6s0;rx&doteqdot=6s1;rz&dotminus=6rc;q2&dotplus=6qc;ny&dotsquare=6u9;xm&doublebarwedge=6x2;11z&downarrow=6mr;i9&downdownarrows=6oa;lc&downharpoonleft=6o3;kv&downharpoonright=6o2;ks&drbkarow=840;182&drcorn=6xr;12p&drcrop=6x8;129&dscr=2kl5;1jx&dscy=ut;cr&dsol=8ae;1cc&dstrok=7l;4z&dtdot=6wh;11i&dtri=7gf;15j&dtrif=7ge;15h&duarr=6ph;mo&duhar=86n;1a5&dwangle=886;1b3&dzcy=v3;d0&dzigrarr=7wf;17r&eDDot=8dz;1eq&eDot=6s1;s0é=6h;3u&easter=8dq;1eg&ecaron=7v;57&ecir=6s6;sbê=6i;3v&ecolon=6s5;s9&ecy=ul;ck&edot=7r;53&ee=6kn;he&efDot=6s2;s2&efr=2ko2;1l6&eg=8ey;1g9è=6g;3t&egs=8eu;1g5&egsdot=8ew;1g7&el=8ex;1g8&elinters=73b;13e&ell=6j7;fv&els=8et;1g3&elsdot=8ev;1g6&emacr=7n;51&empty=6px;n7&emptyset=6px;n5&emptyv=6px;n6&emsp=6bn;d2&emsp13=6bo;d3&emsp14=6bp;d4&eng=97;6h&ensp=6bm;d1&eogon=7t;55&eopf=2kpi;1mf&epar=6vp;103&eparsl=89v;1c6&eplus=8dt;1ek&epsi=qd;97&epsilon=qd;96&epsiv=s5;ae&eqcirc=6s6;sa&eqcolon=6s5;s8&eqsim=6rm;qq&eqslantgtr=8eu;1g4&eqslantless=8et;1g2&equals=1p;p&equest=6sf;sj&equiv=6sh;so&equivDD=8e0;1er&eqvparsl=89x;1c8&erDot=6s3;s4&erarr=86p;1a7&escr=6jz;gs&esdot=6s0;ry&esim=6rm;qr&eta=qf;99ð=6o;41ë=6j;3w&euro=6gc;f2&excl=x;2&exist=6pv;n0&expectation=6k0;gt&exponentiale=6kn;hd&fallingdotseq=6s2;s1&fcy=uc;cb&female=7k0;163&ffilig=1dkz;1ja&fflig=1dkw;1j7&ffllig=1dl0;1jb&ffr=2ko3;1l7&filig=1dkx;1j8&fjlig=2u,2y;15&flat=7l9;16e&fllig=1dky;1j9&fltns=7g1;153&fnof=b6;7v&fopf=2kpj;1mg&forall=6ps;mt&fork=6vo;102&forkv=8gp;1in&fpartint=8b1;1cp½=59;2k&frac13=6kz;hh¼=58;2j&frac15=6l1;hj&frac16=6l5;hn&frac18=6l7;hp&frac23=6l0;hi&frac25=6l2;hk¾=5a;2m&frac35=6l3;hl&frac38=6l8;hq&frac45=6l4;hm&frac56=6l6;ho&frac58=6l9;hr&frac78=6la;hs&frasl=6dg;eq&frown=6xu;12r&fscr=2kl7;1jy&gE=6sn;t8&gEl=8ek;1ft&gacute=dx;7x&gamma=qb;94&gammad=rh;a7&gap=8ee;1fh&gbreve=7z;5b&gcirc=7x;59&gcy=tv;bu&gdot=81;5d&ge=6sl;sx&gel=6vv;10k&geq=6sl;sw&geqq=6sn;t7&geqslant=8e6;1f6&ges=8e6;1f7&gescc=8fd;1gn&gesdot=8e8;1f9&gesdoto=8ea;1fb&gesdotol=8ec;1fd&gesl=6vv,1e68;10h&gesles=8es;1g1&gfr=2ko4;1l8&gg=6sr;ts&ggg=6vt;10b&gimel=6k7;h6&gjcy=ur;cp&gl=6t3;un&glE=8eq;1fz&gla=8f9;1gj&glj=8f8;1gi&gnE=6sp;tg&gnap=8ei;1fp&gnapprox=8ei;1fo&gne=8eg;1fl&gneq=8eg;1fk&gneqq=6sp;tf&gnsim=6w7;10y&gopf=2kpk;1mh&grave=2o;14&gscr=6iy;f9&gsim=6sz;ud&gsime=8em;1fv&gsiml=8eo;1fx>=1q;s>cc=8fb;1gl>cir=8e2;1et>dot=6vr;107>lPar=87p;1aw>quest=8e4;1ev>rapprox=8ee;1fg>rarr=86w;1ad>rdot=6vr;106>reqless=6vv;10j>reqqless=8ek;1fs>rless=6t3;um>rsim=6sz;uc&gvertneqq=6sp,1e68;td&gvnE=6sp,1e68;te&hArr=6ok;m5&hairsp=6bu;da&half=59;2l&hamilt=6iz;fb&hardcy=ui;ch&harr=6ms;id&harrcir=85k;192&harrw=6nh;js&hbar=6j3;fl&hcirc=85;5g&hearts=7l1;16a&heartsuit=7l1;169&hellip=6cm;eb&hercon=6ux;yr&hfr=2ko5;1l9&hksearow=84l;18i&hkswarow=84m;18k&hoarr=6pr;mr&homtht=6rf;q5&hookleftarrow=6nd;jj&hookrightarrow=6ne;jl&hopf=2kpl;1mi&horbar=6c5;do&hscr=2kl9;1jz&hslash=6j3;fi&hstrok=87;5i&hybull=6df;ep&hyphen=6c0;dkí=6l;3y&ic=6eb;f1î=6m;3z&icy=u0;bz&iecy=tx;bw¡=4h;1f&iff=6ok;m6&ifr=2ko6;1laì=6k;3x&ii=6ko;hg&iiiint=8b0;1cn&iiint=6r1;pg&iinfin=89o;1c3&iiota=6jt;gm&ijlig=8j;5t&imacr=8b;5m&image=6j5;fp&imagline=6j4;fm&imagpart=6j5;fo&imath=8h;5r&imof=6uv;yo&imped=c5;7w&in=6q0;nd&incare=6it;f8&infin=6qm;of&infintie=89p;1c4&inodot=8h;5q&int=6qz;pe&intcal=6uy;yt&integers=6jo;gh&intercal=6uy;ys&intlarhk=8bb;1cx&intprod=8cc;1dk&iocy=up;cn&iogon=8f;5o&iopf=2kpm;1mj&iota=qh;9b&iprod=8cc;1dl¿=5b;2n&iscr=2kla;1k0&isin=6q0;nc&isinE=6wp;11r&isindot=6wl;11n&isins=6wk;11l&isinsv=6wj;11k&isinv=6q0;nb&it=6ea;ez&itilde=89;5k&iukcy=uu;csï=6n;40&jcirc=8l;5v&jcy=u1;c0&jfr=2ko7;1lb&jmath=fr;7y&jopf=2kpn;1mk&jscr=2klb;1k1&jsercy=uw;cu&jukcy=us;cq&kappa=qi;9c&kappav=s0;a9&kcedil=8n;5x&kcy=u2;c1&kfr=2ko8;1lc&kgreen=8o;5y&khcy=ud;cc&kjcy=v0;cy&kopf=2kpo;1ml&kscr=2klc;1k2&lAarr=6oq;mf&lArr=6og;ls&lAtail=84b;18a&lBarr=83y;17z&lE=6sm;t2&lEg=8ej;1fr&lHar=86a;19q&lacute=8q;60&laemptyv=88k;1bh&lagran=6j6;ft&lambda=qj;9d&lang=7vs;16z&langd=87l;1as&langle=7vs;16y&lap=8ed;1ff«=4r;1t&larr=6mo;hx&larrb=6p0;mk&larrbfs=84f;18e&larrfs=84d;18c&larrhk=6nd;jk&larrlp=6nf;jo&larrpl=855;18y&larrsim=86r;1a9&larrtl=6n6;j7&lat=8ff;1gp&latail=849;188&late=8fh;1gt&lates=8fh,1e68;1gs&lbarr=83w;17w&lbbrk=7si;16p&lbrace=3f;16&lbrack=2j;v&lbrke=87f;1am&lbrksld=87j;1aq&lbrkslu=87h;1ao&lcaron=8u;64&lcedil=8s;62&lceil=6x4;122&lcub=3f;17&lcy=u3;c2&ldca=852;18v&ldquo=6cc;dz&ldquor=6ce;e3&ldrdhar=86f;19v&ldrushar=85n;195&ldsh=6nm;jz&le=6sk;st&leftarrow=6mo;hv&leftarrowtail=6n6;j6&leftharpoondown=6nx;kd&leftharpoonup=6nw;ka&leftleftarrows=6o7;l6&leftrightarrow=6ms;ic&leftrightarrows=6o6;l4&leftrightharpoons=6ob;lf&leftrightsquigarrow=6nh;jr&leftthreetimes=6vf;zl&leg=6vu;10g&leq=6sk;ss&leqq=6sm;t1&leqslant=8e5;1f0&les=8e5;1f1&lescc=8fc;1gm&lesdot=8e7;1f8&lesdoto=8e9;1fa&lesdotor=8eb;1fc&lesg=6vu,1e68;10d&lesges=8er;1g0&lessapprox=8ed;1fe&lessdot=6vq;104&lesseqgtr=6vu;10f&lesseqqgtr=8ej;1fq&lessgtr=6t2;uj&lesssim=6sy;u9&lfisht=870;1ag&lfloor=6x6;126&lfr=2ko9;1ld&lg=6t2;uk&lgE=8ep;1fy&lhard=6nx;kf&lharu=6nw;kc&lharul=86i;19y&lhblk=7es;14o&ljcy=ux;cv&ll=6sq;tm&llarr=6o7;l7&llcorner=6xq;12m&llhard=86j;19z&lltri=7i2;15w&lmidot=8w;66&lmoust=71s;131&lmoustache=71s;130&lnE=6so;tc&lnap=8eh;1fn&lnapprox=8eh;1fm&lne=8ef;1fj&lneq=8ef;1fi&lneqq=6so;tb&lnsim=6w6;10x&loang=7vw;175&loarr=6pp;mp&lobrk=7vq;16u&longleftarrow=7w5;178&longleftrightarrow=7w7;17e&longmapsto=7wc;17p&longrightarrow=7w6;17b&looparrowleft=6nf;jn&looparrowright=6ng;jp&lopar=879;1ak&lopf=2kpp;1mm&loplus=8bx;1d6&lotimes=8c4;1dc&lowast=6qf;o5&lowbar=2n;12&loz=7gq;15p&lozenge=7gq;15o&lozf=8a3;1ca&lpar=14;b&lparlt=87n;1au&lrarr=6o6;l5&lrcorner=6xr;12o&lrhar=6ob;lg&lrhard=86l;1a1&lrm=6by;di&lrtri=6v3;yx&lsaquo=6d5;ek&lscr=2kld;1k3&lsh=6nk;jw&lsim=6sy;ua&lsime=8el;1fu&lsimg=8en;1fw&lsqb=2j;w&lsquo=6c8;ds&lsquor=6ca;dw&lstrok=8y;68<=1o;n<cc=8fa;1gk<cir=8e1;1es<dot=6vq;105<hree=6vf;zm<imes=6vd;zj<larr=86u;1ac<quest=8e3;1eu<rPar=87q;1ax<ri=7gj;15n<rie=6us;yi<rif=7gi;15l&lurdshar=85m;194&luruhar=86e;19u&lvertneqq=6so,1e68;t9&lvnE=6so,1e68;ta&mDDot=6re;q4¯=4v;20&male=7k2;164&malt=7q8;16m&maltese=7q8;16l&map=6na;jg&mapsto=6na;jf&mapstodown=6nb;ji&mapstoleft=6n8;jb&mapstoup=6n9;jd&marker=7fy;152&mcomma=8bt;1d4&mcy=u4;c3&mdash=6c4;dn&measuredangle=6qp;ok&mfr=2koa;1le&mho=6jr;gjµ=51;29&mid=6qr;oq&midast=16;d&midcir=8hc;1j1·=53;2d&minus=6qa;nu&minusb=6u7;xj&minusd=6rc;q3&minusdu=8bu;1d5&mlcp=8gr;1ip&mldr=6cm;ec&mnplus=6qb;nw&models=6uf;xy&mopf=2kpq;1mn&mp=6qb;nx&mscr=2kle;1k4&mstpos=6ri;qf&mu=qk;9e&multimap=6uw;yp&mumap=6uw;yq&nGg=6vt,mw;10a&nGt=6sr,6he;tp&nGtv=6sr,mw;to&nLeftarrow=6od;lk&nLeftrightarrow=6oe;lm&nLl=6vs,mw;108&nLt=6sq,6he;tj&nLtv=6sq,mw;ti&nRightarrow=6of;lo&nVDash=6un;y7&nVdash=6um;y6&nabla=6pz;n8&nacute=90;6a&nang=6qo,6he;oh&nap=6rt;rb&napE=8ds,mw;1ei&napid=6rv,mw;re&napos=95;6f&napprox=6rt;ra&natur=7la;16g&natural=7la;16f&naturals=6j9;fw =4g;1e&nbump=6ry,mw;rm&nbumpe=6rz,mw;rr&ncap=8cj;1dp&ncaron=94;6e&ncedil=92;6c&ncong=6rr;r2&ncongdot=8dp,mw;1ee&ncup=8ci;1do&ncy=u5;c4&ndash=6c3;dm&ne=6sg;sl&neArr=6on;mb&nearhk=84k;18h&nearr=6mv;im&nearrow=6mv;il&nedot=6s0,mw;rv&nequiv=6si;sq&nesear=84o;18n&nesim=6rm,mw;qo&nexist=6pw;n3&nexists=6pw;n2&nfr=2kob;1lf&ngE=6sn,mw;t4&nge=6sx;u7&ngeq=6sx;u6&ngeqq=6sn,mw;t5&ngeqslant=8e6,mw;1f3&nges=8e6,mw;1f4&ngsim=6t1;uh&ngt=6sv;u1&ngtr=6sv;u0&nhArr=6oe;ln&nharr=6ni;ju&nhpar=8he;1j3&ni=6q3;nk&nis=6ws;11u&nisd=6wq;11s&niv=6q3;nj&njcy=uy;cw&nlArr=6od;ll&nlE=6sm,mw;sy&nlarr=6my;iu&nldr=6cl;ea&nle=6sw;u4&nleftarrow=6my;it&nleftrightarrow=6ni;jt&nleq=6sw;u3&nleqq=6sm,mw;sz&nleqslant=8e5,mw;1ex&nles=8e5,mw;1ey&nless=6su;tx&nlsim=6t0;uf&nlt=6su;ty&nltri=6wa;115&nltrie=6wc;11b&nmid=6qs;ou&nopf=2kpr;1mo¬=4s;1u¬in=6q1;ng¬inE=6wp,mw;11q¬indot=6wl,mw;11m¬inva=6q1;nf¬invb=6wn;11p¬invc=6wm;11o¬ni=6q4;nn¬niva=6q4;nm¬nivb=6wu;11w¬nivc=6wt;11v&npar=6qu;p4&nparallel=6qu;p2&nparsl=8hp,6hx;1j5&npart=6pu,mw;mw&npolint=8b8;1cu&npr=6tc;vd&nprcue=6w0;10q&npre=8fj,mw;1gw&nprec=6tc;vc&npreceq=8fj,mw;1gx&nrArr=6of;lp&nrarr=6mz;iw&nrarrc=84z,mw;18s&nrarrw=6n1,mw;ix&nrightarrow=6mz;iv&nrtri=6wb;118&nrtrie=6wd;11e&nsc=6td;vg&nsccue=6w1;10s&nsce=8fk,mw;1h2&nscr=2klf;1k5&nshortmid=6qs;os&nshortparallel=6qu;p1&nsim=6rl;qm&nsime=6ro;qx&nsimeq=6ro;qw&nsmid=6qs;ot&nspar=6qu;p3&nsqsube=6w2;10u&nsqsupe=6w3;10w&nsub=6tg;vs&nsubE=8g5,mw;1hv&nsube=6tk;w2&nsubset=6te,6he;vi&nsubseteq=6tk;w1&nsubseteqq=8g5,mw;1hw&nsucc=6td;vf&nsucceq=8fk,mw;1h3&nsup=6th;vt&nsupE=8g6,mw;1hz&nsupe=6tl;w5&nsupset=6tf,6he;vn&nsupseteq=6tl;w4&nsupseteqq=8g6,mw;1i0&ntgl=6t5;urñ=6p;42&ntlg=6t4;up&ntriangleleft=6wa;114&ntrianglelefteq=6wc;11a&ntriangleright=6wb;117&ntrianglerighteq=6wd;11d&nu=ql;9f&num=z;5&numero=6ja;fy&numsp=6br;d5&nvDash=6ul;y5&nvHarr=83o;17u&nvap=6rx,6he;ri&nvdash=6uk;y4&nvge=6sl,6he;su&nvgt=1q,6he;q&nvinfin=89q;1c5&nvlArr=83m;17s&nvle=6sk,6he;sr&nvlt=1o,6he;l&nvltrie=6us,6he;yf&nvrArr=83n;17t&nvrtrie=6ut,6he;yj&nvsim=6rg,6he;q6&nwArr=6om;ma&nwarhk=84j;18g&nwarr=6mu;ij&nwarrow=6mu;ii&nwnear=84n;18m&oS=79k;13hó=6r;44&oast=6u3;xd&ocir=6u2;xbô=6s;45&ocy=u6;c5&odash=6u5;xf&odblac=9d;6l&odiv=8c8;1dg&odot=6u1;x9&odsold=88s;1bn&oelig=9f;6n&ofcir=88v;1bp&ofr=2koc;1lg&ogon=kb;87ò=6q;43&ogt=88x;1br&ohbar=88l;1bi&ohm=q1;91&oint=6r2;pk&olarr=6nu;k7&olcir=88u;1bo&olcross=88r;1bm&oline=6da;en&olt=88w;1bq&omacr=99;6j&omega=qx;9u&omicron=qn;9h&omid=88m;1bj&ominus=6ty;x4&oopf=2kps;1mp&opar=88n;1bk&operp=88p;1bl&oplus=6tx;x2&or=6qw;p8&orarr=6nv;k9&ord=8d9;1ea&order=6k4;h1&orderof=6k4;h0ª=4q;1sº=56;2h&origof=6uu;yn&oror=8d2;1e4&orslope=8d3;1e5&orv=8d7;1e8&oscr=6k4;h2ø=6w;4a&osol=6u0;x7õ=6t;46&otimes=6tz;x6&otimesas=8c6;1deö=6u;47&ovbar=6yl;12x&par=6qt;oz¶=52;2a¶llel=6qt;ox&parsim=8hf;1j4&parsl=8hp;1j6&part=6pu;my&pcy=u7;c6&percnt=11;7&period=1a;h&permil=6cw;ed&perp=6ud;xw&pertenk=6cx;ee&pfr=2kod;1lh&phi=qu;9r&phiv=r9;a2&phmmat=6k3;gy&phone=7im;162&pi=qo;9i&pitchfork=6vo;101&piv=ra;a4&planck=6j3;fj&planckh=6j2;fh&plankv=6j3;fk&plus=17;f&plusacir=8bn;1cz&plusb=6u6;xh&pluscir=8bm;1cy&plusdo=6qc;nz&plusdu=8bp;1d1&pluse=8du;1el±=4x;23&plussim=8bq;1d2&plustwo=8br;1d3&pm=4x;24&pointint=8b9;1cv&popf=2kpt;1mq£=4j;1h&pr=6t6;uu&prE=8fn;1h7&prap=8fr;1he&prcue=6t8;v0&pre=8fj;1h0&prec=6t6;ut&precapprox=8fr;1hd&preccurlyeq=6t8;uz&preceq=8fj;1gz&precnapprox=8ft;1hh&precneqq=8fp;1h9&precnsim=6w8;10z&precsim=6ta;v5&prime=6cy;ef&primes=6jd;g2&prnE=8fp;1ha&prnap=8ft;1hi&prnsim=6w8;110&prod=6q7;np&profalar=6y6;12v&profline=6xe;12e&profsurf=6xf;12f&prop=6ql;oe&propto=6ql;oc&prsim=6ta;v6&prurel=6uo;y8&pscr=2klh;1k6&psi=qw;9t&puncsp=6bs;d6&qfr=2koe;1li&qint=8b0;1co&qopf=2kpu;1mr&qprime=6dz;es&qscr=2kli;1k7&quaternions=6j1;ff&quatint=8ba;1cw&quest=1r;t&questeq=6sf;si"=y;4&rAarr=6or;mh&rArr=6oi;lz&rAtail=84c;18b&rBarr=83z;181&rHar=86c;19s&race=6rh,mp;qb&racute=9h;6p&radic=6qi;o8&raemptyv=88j;1bg&rang=7vt;172&rangd=87m;1at&range=885;1b2&rangle=7vt;171»=57;2i&rarr=6mq;i6&rarrap=86t;1ab&rarrb=6p1;mm&rarrbfs=84g;18f&rarrc=84z;18t&rarrfs=84e;18d&rarrhk=6ne;jm&rarrlp=6ng;jq&rarrpl=85h;191&rarrsim=86s;1aa&rarrtl=6n7;j9&rarrw=6n1;iz&ratail=84a;189&ratio=6ra;pz&rationals=6je;g4&rbarr=83x;17y&rbbrk=7sj;16q&rbrace=3h;1b&rbrack=2l;y&rbrke=87g;1an&rbrksld=87i;1ap&rbrkslu=87k;1ar&rcaron=9l;6t&rcedil=9j;6r&rceil=6x5;124&rcub=3h;1c&rcy=u8;c7&rdca=853;18w&rdldhar=86h;19x&rdquo=6cd;e2&rdquor=6cd;e1&rdsh=6nn;k0&real=6jg;g9&realine=6jf;g6&realpart=6jg;g8&reals=6jh;gc&rect=7fx;151®=4u;1y&rfisht=871;1ah&rfloor=6x7;128&rfr=2kof;1lj&rhard=6o1;kr&rharu=6o0;ko&rharul=86k;1a0&rho=qp;9j&rhov=s1;ab&rightarrow=6mq;i4&rightarrowtail=6n7;j8&rightharpoondown=6o1;kp&rightharpoonup=6o0;km&rightleftarrows=6o4;kz&rightleftharpoons=6oc;lh&rightrightarrows=6o9;la&rightsquigarrow=6n1;iy&rightthreetimes=6vg;zn&ring=ka;86&risingdotseq=6s3;s3&rlarr=6o4;l0&rlhar=6oc;lj&rlm=6bz;dj&rmoust=71t;133&rmoustache=71t;132&rnmid=8ha;1iz&roang=7vx;176&roarr=6pq;mq&robrk=7vr;16w&ropar=87a;1al&ropf=2kpv;1ms&roplus=8by;1d7&rotimes=8c5;1dd&rpar=15;c&rpargt=87o;1av&rppolint=8b6;1cs&rrarr=6o9;lb&rsaquo=6d6;el&rscr=2klj;1k8&rsh=6nl;jy&rsqb=2l;z&rsquo=6c9;dv&rsquor=6c9;du&rthree=6vg;zo&rtimes=6ve;zk&rtri=7g9;15d&rtrie=6ut;ym&rtrif=7g8;15b&rtriltri=89a;1by&ruluhar=86g;19w&rx=6ji;ge&sacute=9n;6v&sbquo=6ca;dx&sc=6t7;ux&scE=8fo;1h8&scap=8fs;1hg&scaron=9t;71&sccue=6t9;v3&sce=8fk;1h6&scedil=9r;6z&scirc=9p;6x&scnE=8fq;1hc&scnap=8fu;1hk&scnsim=6w9;112&scpolint=8b7;1ct&scsim=6tb;va&scy=u9;c8&sdot=6v9;zd&sdotb=6u9;xn&sdote=8di;1ec&seArr=6oo;mc&searhk=84l;18j&searr=6mw;ip&searrow=6mw;io§=4n;1l&semi=1n;k&seswar=84p;18p&setminus=6qe;o2&setmn=6qe;o4&sext=7qu;16n&sfr=2kog;1lk&sfrown=6xu;12q&sharp=7lb;16h&shchcy=uh;cg&shcy=ug;cf&shortmid=6qr;oo&shortparallel=6qt;ow­=4t;1v&sigma=qr;9n&sigmaf=qq;9l&sigmav=qq;9m&sim=6rg;qa&simdot=8dm;1ed&sime=6rn;qu&simeq=6rn;qt&simg=8f2;1gb&simgE=8f4;1gd&siml=8f1;1ga&simlE=8f3;1gc&simne=6rq;r0&simplus=8bo;1d0&simrarr=86q;1a8&slarr=6mo;hw&smallsetminus=6qe;o0&smashp=8c3;1db&smeparsl=89w;1c7&smid=6qr;op&smile=6xv;12t&smt=8fe;1go&smte=8fg;1gr&smtes=8fg,1e68;1gq&softcy=uk;cj&sol=1b;i&solb=890;1bu&solbar=6yn;12y&sopf=2kpw;1mt&spades=7kw;166&spadesuit=7kw;165&spar=6qt;oy&sqcap=6tv;wx&sqcaps=6tv,1e68;wv&sqcup=6tw;x0&sqcups=6tw,1e68;wy&sqsub=6tr;wk&sqsube=6tt;wr&sqsubset=6tr;wj&sqsubseteq=6tt;wq&sqsup=6ts;wo&sqsupe=6tu;wu&sqsupset=6ts;wn&sqsupseteq=6tu;wt&squ=7fl;14v&square=7fl;14u&squarf=7fu;14y&squf=7fu;14z&srarr=6mq;i5&sscr=2klk;1k9&ssetmn=6qe;o3&ssmile=6xv;12s&sstarf=6va;ze&star=7ie;161&starf=7id;160&straightepsilon=s5;ac&straightphi=r9;a0&strns=4v;1z&sub=6te;vl&subE=8g5;1hy&subdot=8fx;1hn&sube=6ti;vw&subedot=8g3;1ht&submult=8g1;1hr&subnE=8gb;1i8&subne=6tm;w9&subplus=8fz;1hp&subrarr=86x;1ae&subset=6te;vk&subseteq=6ti;vv&subseteqq=8g5;1hx&subsetneq=6tm;w8&subsetneqq=8gb;1i7&subsim=8g7;1i3&subsub=8gl;1ij&subsup=8gj;1ih&succ=6t7;uw&succapprox=8fs;1hf&succcurlyeq=6t9;v2&succeq=8fk;1h5&succnapprox=8fu;1hj&succneqq=8fq;1hb&succnsim=6w9;111&succsim=6tb;v9&sum=6q9;nt&sung=7l6;16d&sup=6tf;vr¹=55;2g²=4y;25³=4z;26&supE=8g6;1i2&supdot=8fy;1ho&supdsub=8go;1im&supe=6tj;vz&supedot=8g4;1hu&suphsol=7ux;16s&suphsub=8gn;1il&suplarr=86z;1af&supmult=8g2;1hs&supnE=8gc;1ic&supne=6tn;wd&supplus=8g0;1hq&supset=6tf;vq&supseteq=6tj;vy&supseteqq=8g6;1i1&supsetneq=6tn;wc&supsetneqq=8gc;1ib&supsim=8g8;1i4&supsub=8gk;1ii&supsup=8gm;1ik&swArr=6op;md&swarhk=84m;18l&swarr=6mx;is&swarrow=6mx;ir&swnwar=84q;18rß=67;3k&target=6xi;12h&tau=qs;9o&tbrk=71w;135&tcaron=9x;75&tcedil=9v;73&tcy=ua;c9&tdot=6hn;f4&telrec=6xh;12g&tfr=2koh;1ll&there4=6r8;pv&therefore=6r8;pu&theta=qg;9a&thetasym=r5;9v&thetav=r5;9x&thickapprox=6rs;r3&thicksim=6rg;q7&thinsp=6bt;d8&thkap=6rs;r7&thksim=6rg;q8þ=72;4g&tilde=kc;89×=5z;3c×b=6u8;xl×bar=8c1;1da×d=8c0;1d9&tint=6r1;ph&toea=84o;18o&top=6uc;xt&topbot=6ye;12w&topcir=8hd;1j2&topf=2kpx;1mu&topfork=8gq;1io&tosa=84p;18q&tprime=6d0;eh&trade=6jm;gg&triangle=7g5;158&triangledown=7gf;15i&triangleleft=7gj;15m&trianglelefteq=6us;yh&triangleq=6sc;sg&triangleright=7g9;15c&trianglerighteq=6ut;yl&tridot=7ho;15r&trie=6sc;sh&triminus=8ca;1di&triplus=8c9;1dh&trisb=899;1bx&tritime=8cb;1dj&trpezium=736;13d&tscr=2kll;1ka&tscy=ue;cd&tshcy=uz;cx&tstrok=9z;77&twixt=6ss;tu&twoheadleftarrow=6n2;j0&twoheadrightarrow=6n4;j3&uArr=6oh;lv&uHar=86b;19rú=6y;4c&uarr=6mp;i1&ubrcy=v2;cz&ubreve=a5;7dû=6z;4d&ucy=ub;ca&udarr=6o5;l2&udblac=a9;7h&udhar=86m;1a3&ufisht=872;1ai&ufr=2koi;1lmù=6x;4b&uharl=6nz;kl&uharr=6ny;ki&uhblk=7eo;14n&ulcorn=6xo;12j&ulcorner=6xo;12i&ulcrop=6xb;12c&ultri=7i0;15u&umacr=a3;7b¨=4o;1p&uogon=ab;7j&uopf=2kpy;1mv&uparrow=6mp;i0&updownarrow=6mt;if&upharpoonleft=6nz;kj&upharpoonright=6ny;kg&uplus=6tq;wg&upsi=qt;9q&upsih=r6;9y&upsilon=qt;9p&upuparrows=6o8;l8&urcorn=6xp;12l&urcorner=6xp;12k&urcrop=6xa;12b&uring=a7;7f&urtri=7i1;15v&uscr=2klm;1kb&utdot=6wg;11h&utilde=a1;79&utri=7g5;159&utrif=7g4;157&uuarr=6o8;l9ü=70;4e&uwangle=887;1b4&vArr=6ol;m9&vBar=8h4;1iu&vBarv=8h5;1iv&vDash=6ug;y0&vangrt=87w;1az&varepsilon=s5;ad&varkappa=s0;a8&varnothing=6px;n4&varphi=r9;a1&varpi=ra;a3&varpropto=6ql;ob&varr=6mt;ig&varrho=s1;aa&varsigma=qq;9k&varsubsetneq=6tm,1e68;w6&varsubsetneqq=8gb,1e68;1i5&varsupsetneq=6tn,1e68;wa&varsupsetneqq=8gc,1e68;1i9&vartheta=r5;9w&vartriangleleft=6uq;y9&vartriangleright=6ur;yc&vcy=tu;bt&vdash=6ua;xp&vee=6qw;p7&veebar=6uz;yu&veeeq=6sa;sf&vellip=6we;11f&verbar=3g;19&vert=3g;1a&vfr=2koj;1ln&vltri=6uq;yb&vnsub=6te,6he;vj&vnsup=6tf,6he;vo&vopf=2kpz;1mw&vprop=6ql;od&vrtri=6ur;ye&vscr=2kln;1kc&vsubnE=8gb,1e68;1i6&vsubne=6tm,1e68;w7&vsupnE=8gc,1e68;1ia&vsupne=6tn,1e68;wb&vzigzag=87u;1ay&wcirc=ad;7l&wedbar=8db;1eb&wedge=6qv;p5&wedgeq=6s9;se&weierp=6jc;g0&wfr=2kok;1lo&wopf=2kq0;1mx&wp=6jc;g1&wr=6rk;qk&wreath=6rk;qj&wscr=2klo;1kd&xcap=6v6;z6&xcirc=7hr;15t&xcup=6v7;z9&xdtri=7gd;15f&xfr=2kol;1lp&xhArr=7wa;17o&xharr=7w7;17f&xi=qm;9g&xlArr=7w8;17i&xlarr=7w5;179&xmap=7wc;17q&xnis=6wr;11t&xodot=8ao;1ce&xopf=2kq1;1my&xoplus=8ap;1cg&xotime=8aq;1ci&xrArr=7w9;17l&xrarr=7w6;17c&xscr=2klp;1ke&xsqcup=8au;1cm&xuplus=8as;1ck&xutri=7g3;155&xvee=6v5;z2&xwedge=6v4;yzý=71;4f&yacy=un;cm&ycirc=af;7n&ycy=uj;ci¥=4l;1j&yfr=2kom;1lq&yicy=uv;ct&yopf=2kq2;1mz&yscr=2klq;1kf&yucy=um;clÿ=73;4h&zacute=ai;7q&zcaron=am;7u&zcy=tz;by&zdot=ak;7s&zeetrf=6js;gk&zeta=qe;98&zfr=2kon;1lr&zhcy=ty;bx&zigrarr=6ot;mi&zopf=2kq3;1n0&zscr=2klr;1kg&zwj=6bx;dh&zwnj=6bw;dg&"; + static final String xmlPoints; + static final String basePoints; + static final String fullPoints; + + // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6447475 + // allocated in static here so not inlined in users; saves 16K from .jar (!) + static { + xmlPoints = "amp=12;1>=1q;3<=1o;2"=y;0&"; + basePoints = "AElig=5i;1c&=12;2Á=5d;17Â=5e;18À=5c;16Å=5h;1bÃ=5f;19Ä=5g;1a©=4p;hÇ=5j;1dÐ=5s;1mÉ=5l;1fÊ=5m;1gÈ=5k;1eË=5n;1h>=1q;6Í=5p;1jÎ=5q;1kÌ=5o;1iÏ=5r;1l<=1o;4Ñ=5t;1nÓ=5v;1pÔ=5w;1qÒ=5u;1oØ=60;1uÕ=5x;1rÖ=5y;1s"=y;0®=4u;nÞ=66;20Ú=62;1wÛ=63;1xÙ=61;1vÜ=64;1yÝ=65;1zá=69;23â=6a;24´=50;uæ=6e;28à=68;22&=12;3å=6d;27ã=6b;25ä=6c;26¦=4m;eç=6f;29¸=54;y¢=4i;a©=4p;i¤=4k;c°=4w;q÷=6v;2pé=6h;2bê=6i;2cè=6g;2að=6o;2ië=6j;2d½=59;13¼=58;12¾=5a;14>=1q;7í=6l;2fî=6m;2g¡=4h;9ì=6k;2e¿=5b;15ï=6n;2h«=4r;k<=1o;5¯=4v;pµ=51;v·=53;x =4g;8¬=4s;lñ=6p;2jó=6r;2lô=6s;2mò=6q;2kª=4q;jº=56;10ø=6w;2qõ=6t;2nö=6u;2o¶=52;w±=4x;r£=4j;b"=y;1»=57;11®=4u;o§=4n;f­=4t;m¹=55;z²=4y;s³=4z;tß=67;21þ=72;2w×=5z;1tú=6y;2sû=6z;2tù=6x;2r¨=4o;gü=70;2uý=71;2v¥=4l;dÿ=73;2x&"; + fullPoints = "AElig=5i;2v&=12;8Á=5d;2p&Abreve=76;4kÂ=5e;2q&Acy=sw;av&Afr=2kn8;1khÀ=5c;2o&Alpha=pd;8d&Amacr=74;4i&And=8cz;1e1&Aogon=78;4m&Aopf=2koo;1ls&ApplyFunction=6e9;ewÅ=5h;2t&Ascr=2kkc;1jc&Assign=6s4;s6Ã=5f;2rÄ=5g;2s&Backslash=6qe;o1&Barv=8h3;1it&Barwed=6x2;120&Bcy=sx;aw&Because=6r9;pw&Bernoullis=6jw;gn&Beta=pe;8e&Bfr=2kn9;1ki&Bopf=2kop;1lt&Breve=k8;82&Bscr=6jw;gp&Bumpeq=6ry;ro&CHcy=tj;bi©=4p;1q&Cacute=7a;4o&Cap=6vm;zz&CapitalDifferentialD=6kl;h8&Cayleys=6jx;gq&Ccaron=7g;4uÇ=5j;2w&Ccirc=7c;4q&Cconint=6r4;pn&Cdot=7e;4s&Cedilla=54;2e&CenterDot=53;2b&Cfr=6jx;gr&Chi=pz;8y&CircleDot=6u1;x8&CircleMinus=6ty;x3&CirclePlus=6tx;x1&CircleTimes=6tz;x5&ClockwiseContourIntegral=6r6;pp&CloseCurlyDoubleQuote=6cd;e0&CloseCurlyQuote=6c9;dt&Colon=6rb;q1&Colone=8dw;1en&Congruent=6sh;sn&Conint=6r3;pm&ContourIntegral=6r2;pi&Copf=6iq;f7&Coproduct=6q8;nq&CounterClockwiseContourIntegral=6r7;pr&Cross=8bz;1d8&Cscr=2kke;1jd&Cup=6vn;100&CupCap=6rx;rk&DD=6kl;h9&DDotrahd=841;184&DJcy=si;ai&DScy=sl;al&DZcy=sv;au&Dagger=6ch;e7&Darr=6n5;j5&Dashv=8h0;1ir&Dcaron=7i;4w&Dcy=t0;az&Del=6pz;n9&Delta=pg;8g&Dfr=2knb;1kj&DiacriticalAcute=50;27&DiacriticalDot=k9;84&DiacriticalDoubleAcute=kd;8a&DiacriticalGrave=2o;13&DiacriticalTilde=kc;88&Diamond=6v8;za&DifferentialD=6km;ha&Dopf=2kor;1lu&Dot=4o;1n&DotDot=6ho;f5&DotEqual=6s0;rw&DoubleContourIntegral=6r3;pl&DoubleDot=4o;1m&DoubleDownArrow=6oj;m0&DoubleLeftArrow=6og;lq&DoubleLeftRightArrow=6ok;m3&DoubleLeftTee=8h0;1iq&DoubleLongLeftArrow=7w8;17g&DoubleLongLeftRightArrow=7wa;17m&DoubleLongRightArrow=7w9;17j&DoubleRightArrow=6oi;lw&DoubleRightTee=6ug;xz&DoubleUpArrow=6oh;lt&DoubleUpDownArrow=6ol;m7&DoubleVerticalBar=6qt;ov&DownArrow=6mr;i8&DownArrowBar=843;186&DownArrowUpArrow=6ph;mn&DownBreve=lt;8c&DownLeftRightVector=85s;198&DownLeftTeeVector=866;19m&DownLeftVector=6nx;ke&DownLeftVectorBar=85y;19e&DownRightTeeVector=867;19n&DownRightVector=6o1;kq&DownRightVectorBar=85z;19f&DownTee=6uc;xs&DownTeeArrow=6nb;jh&Downarrow=6oj;m1&Dscr=2kkf;1je&Dstrok=7k;4y&ENG=96;6gÐ=5s;35É=5l;2y&Ecaron=7u;56Ê=5m;2z&Ecy=tp;bo&Edot=7q;52&Efr=2knc;1kkÈ=5k;2x&Element=6q0;na&Emacr=7m;50&EmptySmallSquare=7i3;15x&EmptyVerySmallSquare=7fv;150&Eogon=7s;54&Eopf=2kos;1lv&Epsilon=ph;8h&Equal=8dx;1eo&EqualTilde=6rm;qp&Equilibrium=6oc;li&Escr=6k0;gu&Esim=8dv;1em&Eta=pj;8jË=5n;30&Exists=6pv;mz&ExponentialE=6kn;hc&Fcy=tg;bf&Ffr=2knd;1kl&FilledSmallSquare=7i4;15y&FilledVerySmallSquare=7fu;14w&Fopf=2kot;1lw&ForAll=6ps;ms&Fouriertrf=6k1;gv&Fscr=6k1;gw&GJcy=sj;aj>=1q;r&Gamma=pf;8f&Gammad=rg;a5&Gbreve=7y;5a&Gcedil=82;5e&Gcirc=7w;58&Gcy=sz;ay&Gdot=80;5c&Gfr=2kne;1km&Gg=6vt;10c&Gopf=2kou;1lx&GreaterEqual=6sl;sv&GreaterEqualLess=6vv;10i&GreaterFullEqual=6sn;t6&GreaterGreater=8f6;1gh&GreaterLess=6t3;ul&GreaterSlantEqual=8e6;1f5&GreaterTilde=6sz;ub&Gscr=2kki;1jf&Gt=6sr;tr&HARDcy=tm;bl&Hacek=jr;80&Hat=2m;10&Hcirc=84;5f&Hfr=6j0;fe&HilbertSpace=6iz;fa&Hopf=6j1;fg&HorizontalLine=7b4;13i&Hscr=6iz;fc&Hstrok=86;5h&HumpDownHump=6ry;rn&HumpEqual=6rz;rs&IEcy=t1;b0&IJlig=8i;5s&IOcy=sh;ahÍ=5p;32Î=5q;33&Icy=t4;b3&Idot=8g;5p&Ifr=6j5;fqÌ=5o;31&Im=6j5;fr&Imacr=8a;5l&ImaginaryI=6ko;hf&Implies=6oi;ly&Int=6r0;pf&Integral=6qz;pd&Intersection=6v6;z4&InvisibleComma=6eb;f0&InvisibleTimes=6ea;ey&Iogon=8e;5n&Iopf=2kow;1ly&Iota=pl;8l&Iscr=6j4;fn&Itilde=88;5j&Iukcy=sm;amÏ=5r;34&Jcirc=8k;5u&Jcy=t5;b4&Jfr=2knh;1kn&Jopf=2kox;1lz&Jscr=2kkl;1jg&Jsercy=so;ao&Jukcy=sk;ak&KHcy=th;bg&KJcy=ss;as&Kappa=pm;8m&Kcedil=8m;5w&Kcy=t6;b5&Kfr=2kni;1ko&Kopf=2koy;1m0&Kscr=2kkm;1jh&LJcy=sp;ap<=1o;m&Lacute=8p;5z&Lambda=pn;8n&Lang=7vu;173&Laplacetrf=6j6;fs&Larr=6n2;j1&Lcaron=8t;63&Lcedil=8r;61&Lcy=t7;b6&LeftAngleBracket=7vs;16x&LeftArrow=6mo;hu&LeftArrowBar=6p0;mj&LeftArrowRightArrow=6o6;l3&LeftCeiling=6x4;121&LeftDoubleBracket=7vq;16t&LeftDownTeeVector=869;19p&LeftDownVector=6o3;kw&LeftDownVectorBar=861;19h&LeftFloor=6x6;125&LeftRightArrow=6ms;ib&LeftRightVector=85q;196&LeftTee=6ub;xq&LeftTeeArrow=6n8;ja&LeftTeeVector=862;19i&LeftTriangle=6uq;ya&LeftTriangleBar=89b;1c0&LeftTriangleEqual=6us;yg&LeftUpDownVector=85t;199&LeftUpTeeVector=868;19o&LeftUpVector=6nz;kk&LeftUpVectorBar=860;19g&LeftVector=6nw;kb&LeftVectorBar=85u;19a&Leftarrow=6og;lr&Leftrightarrow=6ok;m4&LessEqualGreater=6vu;10e&LessFullEqual=6sm;t0&LessGreater=6t2;ui&LessLess=8f5;1gf&LessSlantEqual=8e5;1ez&LessTilde=6sy;u8&Lfr=2knj;1kp&Ll=6vs;109&Lleftarrow=6oq;me&Lmidot=8v;65&LongLeftArrow=7w5;177&LongLeftRightArrow=7w7;17d&LongRightArrow=7w6;17a&Longleftarrow=7w8;17h&Longleftrightarrow=7wa;17n&Longrightarrow=7w9;17k&Lopf=2koz;1m1&LowerLeftArrow=6mx;iq&LowerRightArrow=6mw;in&Lscr=6j6;fu&Lsh=6nk;jv&Lstrok=8x;67&Lt=6sq;tl&Map=83p;17v&Mcy=t8;b7&MediumSpace=6e7;eu&Mellintrf=6k3;gx&Mfr=2knk;1kq&MinusPlus=6qb;nv&Mopf=2kp0;1m2&Mscr=6k3;gz&Mu=po;8o&NJcy=sq;aq&Nacute=8z;69&Ncaron=93;6d&Ncedil=91;6b&Ncy=t9;b8&NegativeMediumSpace=6bv;dc&NegativeThickSpace=6bv;dd&NegativeThinSpace=6bv;de&NegativeVeryThinSpace=6bv;db&NestedGreaterGreater=6sr;tq&NestedLessLess=6sq;tk&NewLine=a;1&Nfr=2knl;1kr&NoBreak=6e8;ev&NonBreakingSpace=4g;1d&Nopf=6j9;fx&Not=8h8;1ix&NotCongruent=6si;sp&NotCupCap=6st;tv&NotDoubleVerticalBar=6qu;p0&NotElement=6q1;ne&NotEqual=6sg;sk&NotEqualTilde=6rm,mw;qn&NotExists=6pw;n1&NotGreater=6sv;tz&NotGreaterEqual=6sx;u5&NotGreaterFullEqual=6sn,mw;t3&NotGreaterGreater=6sr,mw;tn&NotGreaterLess=6t5;uq&NotGreaterSlantEqual=8e6,mw;1f2&NotGreaterTilde=6t1;ug&NotHumpDownHump=6ry,mw;rl&NotHumpEqual=6rz,mw;rq&NotLeftTriangle=6wa;113&NotLeftTriangleBar=89b,mw;1bz&NotLeftTriangleEqual=6wc;119&NotLess=6su;tw&NotLessEqual=6sw;u2&NotLessGreater=6t4;uo&NotLessLess=6sq,mw;th&NotLessSlantEqual=8e5,mw;1ew&NotLessTilde=6t0;ue&NotNestedGreaterGreater=8f6,mw;1gg&NotNestedLessLess=8f5,mw;1ge&NotPrecedes=6tc;vb&NotPrecedesEqual=8fj,mw;1gv&NotPrecedesSlantEqual=6w0;10p&NotReverseElement=6q4;nl&NotRightTriangle=6wb;116&NotRightTriangleBar=89c,mw;1c1&NotRightTriangleEqual=6wd;11c&NotSquareSubset=6tr,mw;wh&NotSquareSubsetEqual=6w2;10t&NotSquareSuperset=6ts,mw;wl&NotSquareSupersetEqual=6w3;10v&NotSubset=6te,6he;vh&NotSubsetEqual=6tk;w0&NotSucceeds=6td;ve&NotSucceedsEqual=8fk,mw;1h1&NotSucceedsSlantEqual=6w1;10r&NotSucceedsTilde=6tb,mw;v7&NotSuperset=6tf,6he;vm&NotSupersetEqual=6tl;w3&NotTilde=6rl;ql&NotTildeEqual=6ro;qv&NotTildeFullEqual=6rr;r1&NotTildeTilde=6rt;r9&NotVerticalBar=6qs;or&Nscr=2kkp;1jiÑ=5t;36&Nu=pp;8p&OElig=9e;6mÓ=5v;38Ô=5w;39&Ocy=ta;b9&Odblac=9c;6k&Ofr=2knm;1ksÒ=5u;37&Omacr=98;6i&Omega=q1;90&Omicron=pr;8r&Oopf=2kp2;1m3&OpenCurlyDoubleQuote=6cc;dy&OpenCurlyQuote=6c8;dr&Or=8d0;1e2&Oscr=2kkq;1jjØ=60;3dÕ=5x;3a&Otimes=8c7;1dfÖ=5y;3b&OverBar=6da;em&OverBrace=732;13b&OverBracket=71w;134&OverParenthesis=730;139&PartialD=6pu;mx&Pcy=tb;ba&Pfr=2knn;1kt&Phi=py;8x&Pi=ps;8s&PlusMinus=4x;22&Poincareplane=6j0;fd&Popf=6jd;g3&Pr=8fv;1hl&Precedes=6t6;us&PrecedesEqual=8fj;1gy&PrecedesSlantEqual=6t8;uy&PrecedesTilde=6ta;v4&Prime=6cz;eg&Product=6q7;no&Proportion=6rb;q0&Proportional=6ql;oa&Pscr=2kkr;1jk&Psi=q0;8z"=y;3&Qfr=2kno;1ku&Qopf=6je;g5&Qscr=2kks;1jl&RBarr=840;183®=4u;1x&Racute=9g;6o&Rang=7vv;174&Rarr=6n4;j4&Rarrtl=846;187&Rcaron=9k;6s&Rcedil=9i;6q&Rcy=tc;bb&Re=6jg;gb&ReverseElement=6q3;nh&ReverseEquilibrium=6ob;le&ReverseUpEquilibrium=86n;1a4&Rfr=6jg;ga&Rho=pt;8t&RightAngleBracket=7vt;170&RightArrow=6mq;i3&RightArrowBar=6p1;ml&RightArrowLeftArrow=6o4;ky&RightCeiling=6x5;123&RightDoubleBracket=7vr;16v&RightDownTeeVector=865;19l&RightDownVector=6o2;kt&RightDownVectorBar=85x;19d&RightFloor=6x7;127&RightTee=6ua;xo&RightTeeArrow=6na;je&RightTeeVector=863;19j&RightTriangle=6ur;yd&RightTriangleBar=89c;1c2&RightTriangleEqual=6ut;yk&RightUpDownVector=85r;197&RightUpTeeVector=864;19k&RightUpVector=6ny;kh&RightUpVectorBar=85w;19c&RightVector=6o0;kn&RightVectorBar=85v;19b&Rightarrow=6oi;lx&Ropf=6jh;gd&RoundImplies=86o;1a6&Rrightarrow=6or;mg&Rscr=6jf;g7&Rsh=6nl;jx&RuleDelayed=8ac;1cb&SHCHcy=tl;bk&SHcy=tk;bj&SOFTcy=to;bn&Sacute=9m;6u&Sc=8fw;1hm&Scaron=9s;70&Scedil=9q;6y&Scirc=9o;6w&Scy=td;bc&Sfr=2knq;1kv&ShortDownArrow=6mr;i7&ShortLeftArrow=6mo;ht&ShortRightArrow=6mq;i2&ShortUpArrow=6mp;hy&Sigma=pv;8u&SmallCircle=6qg;o6&Sopf=2kp6;1m4&Sqrt=6qi;o9&Square=7fl;14t&SquareIntersection=6tv;ww&SquareSubset=6tr;wi&SquareSubsetEqual=6tt;wp&SquareSuperset=6ts;wm&SquareSupersetEqual=6tu;ws&SquareUnion=6tw;wz&Sscr=2kku;1jm&Star=6va;zf&Sub=6vk;zw&Subset=6vk;zv&SubsetEqual=6ti;vu&Succeeds=6t7;uv&SucceedsEqual=8fk;1h4&SucceedsSlantEqual=6t9;v1&SucceedsTilde=6tb;v8&SuchThat=6q3;ni&Sum=6q9;ns&Sup=6vl;zy&Superset=6tf;vp&SupersetEqual=6tj;vx&Supset=6vl;zxÞ=66;3j&TRADE=6jm;gf&TSHcy=sr;ar&TScy=ti;bh&Tab=9;0&Tau=pw;8v&Tcaron=9w;74&Tcedil=9u;72&Tcy=te;bd&Tfr=2knr;1kw&Therefore=6r8;pt&Theta=pk;8k&ThickSpace=6e7,6bu;et&ThinSpace=6bt;d7&Tilde=6rg;q9&TildeEqual=6rn;qs&TildeFullEqual=6rp;qy&TildeTilde=6rs;r4&Topf=2kp7;1m5&TripleDot=6hn;f3&Tscr=2kkv;1jn&Tstrok=9y;76Ú=62;3f&Uarr=6n3;j2&Uarrocir=85l;193&Ubrcy=su;at&Ubreve=a4;7cÛ=63;3g&Ucy=tf;be&Udblac=a8;7g&Ufr=2kns;1kxÙ=61;3e&Umacr=a2;7a&UnderBar=2n;11&UnderBrace=733;13c&UnderBracket=71x;136&UnderParenthesis=731;13a&Union=6v7;z8&UnionPlus=6tq;wf&Uogon=aa;7i&Uopf=2kp8;1m6&UpArrow=6mp;hz&UpArrowBar=842;185&UpArrowDownArrow=6o5;l1&UpDownArrow=6mt;ie&UpEquilibrium=86m;1a2&UpTee=6ud;xv&UpTeeArrow=6n9;jc&Uparrow=6oh;lu&Updownarrow=6ol;m8&UpperLeftArrow=6mu;ih&UpperRightArrow=6mv;ik&Upsi=r6;9z&Upsilon=px;8w&Uring=a6;7e&Uscr=2kkw;1jo&Utilde=a0;78Ü=64;3h&VDash=6uj;y3&Vbar=8h7;1iw&Vcy=sy;ax&Vdash=6uh;y1&Vdashl=8h2;1is&Vee=6v5;z3&Verbar=6c6;dp&Vert=6c6;dq&VerticalBar=6qr;on&VerticalLine=3g;18&VerticalSeparator=7rs;16o&VerticalTilde=6rk;qi&VeryThinSpace=6bu;d9&Vfr=2knt;1ky&Vopf=2kp9;1m7&Vscr=2kkx;1jp&Vvdash=6ui;y2&Wcirc=ac;7k&Wedge=6v4;z0&Wfr=2knu;1kz&Wopf=2kpa;1m8&Wscr=2kky;1jq&Xfr=2knv;1l0&Xi=pq;8q&Xopf=2kpb;1m9&Xscr=2kkz;1jr&YAcy=tr;bq&YIcy=sn;an&YUcy=tq;bpÝ=65;3i&Ycirc=ae;7m&Ycy=tn;bm&Yfr=2knw;1l1&Yopf=2kpc;1ma&Yscr=2kl0;1js&Yuml=ag;7o&ZHcy=t2;b1&Zacute=ah;7p&Zcaron=al;7t&Zcy=t3;b2&Zdot=aj;7r&ZeroWidthSpace=6bv;df&Zeta=pi;8i&Zfr=6js;gl&Zopf=6jo;gi&Zscr=2kl1;1jtá=69;3m&abreve=77;4l&ac=6ri;qg&acE=6ri,mr;qe&acd=6rj;qhâ=6a;3n´=50;28&acy=ts;bræ=6e;3r&af=6e9;ex&afr=2kny;1l2à=68;3l&alefsym=6k5;h3&aleph=6k5;h4&alpha=q9;92&amacr=75;4j&amalg=8cf;1dm&=12;9&and=6qv;p6&andand=8d1;1e3&andd=8d8;1e9&andslope=8d4;1e6&andv=8d6;1e7&ang=6qo;oj&ange=884;1b1&angle=6qo;oi&angmsd=6qp;ol&angmsdaa=888;1b5&angmsdab=889;1b6&angmsdac=88a;1b7&angmsdad=88b;1b8&angmsdae=88c;1b9&angmsdaf=88d;1ba&angmsdag=88e;1bb&angmsdah=88f;1bc&angrt=6qn;og&angrtvb=6v2;yw&angrtvbd=87x;1b0&angsph=6qq;om&angst=5h;2u&angzarr=70c;12z&aogon=79;4n&aopf=2kpe;1mb&ap=6rs;r8&apE=8ds;1ej&apacir=8dr;1eh&ape=6ru;rd&apid=6rv;rf&apos=13;a&approx=6rs;r5&approxeq=6ru;rcå=6d;3q&ascr=2kl2;1ju&ast=16;e&asymp=6rs;r6&asympeq=6rx;rjã=6b;3oä=6c;3p&awconint=6r7;ps&awint=8b5;1cr&bNot=8h9;1iy&backcong=6rw;rg&backepsilon=s6;af&backprime=6d1;ei&backsim=6rh;qc&backsimeq=6vh;zp&barvee=6v1;yv&barwed=6x1;11y&barwedge=6x1;11x&bbrk=71x;137&bbrktbrk=71y;138&bcong=6rw;rh&bcy=tt;bs&bdquo=6ce;e4&becaus=6r9;py&because=6r9;px&bemptyv=88g;1bd&bepsi=s6;ag&bernou=6jw;go&beta=qa;93&beth=6k6;h5&between=6ss;tt&bfr=2knz;1l3&bigcap=6v6;z5&bigcirc=7hr;15s&bigcup=6v7;z7&bigodot=8ao;1cd&bigoplus=8ap;1cf&bigotimes=8aq;1ch&bigsqcup=8au;1cl&bigstar=7id;15z&bigtriangledown=7gd;15e&bigtriangleup=7g3;154&biguplus=8as;1cj&bigvee=6v5;z1&bigwedge=6v4;yy&bkarow=83x;17x&blacklozenge=8a3;1c9&blacksquare=7fu;14x&blacktriangle=7g4;156&blacktriangledown=7ge;15g&blacktriangleleft=7gi;15k&blacktriangleright=7g8;15a&blank=74z;13f&blk12=7f6;14r&blk14=7f5;14q&blk34=7f7;14s&block=7ew;14p&bne=1p,6hx;o&bnequiv=6sh,6hx;sm&bnot=6xc;12d&bopf=2kpf;1mc&bot=6ud;xx&bottom=6ud;xu&bowtie=6vc;zi&boxDL=7dj;141&boxDR=7dg;13y&boxDl=7di;140&boxDr=7df;13x&boxH=7dc;13u&boxHD=7dy;14g&boxHU=7e1;14j&boxHd=7dw;14e&boxHu=7dz;14h&boxUL=7dp;147&boxUR=7dm;144&boxUl=7do;146&boxUr=7dl;143&boxV=7dd;13v&boxVH=7e4;14m&boxVL=7dv;14d&boxVR=7ds;14a&boxVh=7e3;14l&boxVl=7du;14c&boxVr=7dr;149&boxbox=895;1bw&boxdL=7dh;13z&boxdR=7de;13w&boxdl=7bk;13m&boxdr=7bg;13l&boxh=7b4;13j&boxhD=7dx;14f&boxhU=7e0;14i&boxhd=7cc;13r&boxhu=7ck;13s&boxminus=6u7;xi&boxplus=6u6;xg&boxtimes=6u8;xk&boxuL=7dn;145&boxuR=7dk;142&boxul=7bs;13o&boxur=7bo;13n&boxv=7b6;13k&boxvH=7e2;14k&boxvL=7dt;14b&boxvR=7dq;148&boxvh=7cs;13t&boxvl=7c4;13q&boxvr=7bw;13p&bprime=6d1;ej&breve=k8;83¦=4m;1k&bscr=2kl3;1jv&bsemi=6dr;er&bsim=6rh;qd&bsime=6vh;zq&bsol=2k;x&bsolb=891;1bv&bsolhsub=7uw;16r&bull=6ci;e9&bullet=6ci;e8&bump=6ry;rp&bumpE=8fi;1gu&bumpe=6rz;ru&bumpeq=6rz;rt&cacute=7b;4p&cap=6qx;pa&capand=8ck;1dq&capbrcup=8cp;1dv&capcap=8cr;1dx&capcup=8cn;1dt&capdot=8cg;1dn&caps=6qx,1e68;p9&caret=6dd;eo&caron=jr;81&ccaps=8ct;1dz&ccaron=7h;4vç=6f;3s&ccirc=7d;4r&ccups=8cs;1dy&ccupssm=8cw;1e0&cdot=7f;4t¸=54;2f&cemptyv=88i;1bf¢=4i;1g¢erdot=53;2c&cfr=2ko0;1l4&chcy=uf;ce&check=7pv;16j&checkmark=7pv;16i&chi=qv;9s&cir=7gr;15q&cirE=88z;1bt&circ=jq;7z&circeq=6s7;sc&circlearrowleft=6nu;k6&circlearrowright=6nv;k8&circledR=4u;1w&circledS=79k;13g&circledast=6u3;xc&circledcirc=6u2;xa&circleddash=6u5;xe&cire=6s7;sd&cirfnint=8b4;1cq&cirmid=8hb;1j0&cirscir=88y;1bs&clubs=7kz;168&clubsuit=7kz;167&colon=1m;j&colone=6s4;s7&coloneq=6s4;s5&comma=18;g&commat=1s;u&comp=6pt;mv&compfn=6qg;o7&complement=6pt;mu&complexes=6iq;f6&cong=6rp;qz&congdot=8dp;1ef&conint=6r2;pj&copf=2kpg;1md&coprod=6q8;nr©=4p;1r©sr=6jb;fz&crarr=6np;k1&cross=7pz;16k&cscr=2kl4;1jw&csub=8gf;1id&csube=8gh;1if&csup=8gg;1ie&csupe=8gi;1ig&ctdot=6wf;11g&cudarrl=854;18x&cudarrr=851;18u&cuepr=6vy;10m&cuesc=6vz;10o&cularr=6nq;k3&cularrp=859;190&cup=6qy;pc&cupbrcap=8co;1du&cupcap=8cm;1ds&cupcup=8cq;1dw&cupdot=6tp;we&cupor=8cl;1dr&cups=6qy,1e68;pb&curarr=6nr;k5&curarrm=858;18z&curlyeqprec=6vy;10l&curlyeqsucc=6vz;10n&curlyvee=6vi;zr&curlywedge=6vj;zt¤=4k;1i&curvearrowleft=6nq;k2&curvearrowright=6nr;k4&cuvee=6vi;zs&cuwed=6vj;zu&cwconint=6r6;pq&cwint=6r5;po&cylcty=6y5;12u&dArr=6oj;m2&dHar=86d;19t&dagger=6cg;e5&daleth=6k8;h7&darr=6mr;ia&dash=6c0;dl&dashv=6ub;xr&dbkarow=83z;180&dblac=kd;8b&dcaron=7j;4x&dcy=tw;bv&dd=6km;hb&ddagger=6ch;e6&ddarr=6oa;ld&ddotseq=8dz;1ep°=4w;21&delta=qc;95&demptyv=88h;1be&dfisht=873;1aj&dfr=2ko1;1l5&dharl=6o3;kx&dharr=6o2;ku&diam=6v8;zc&diamond=6v8;zb&diamondsuit=7l2;16b&diams=7l2;16c&die=4o;1o&digamma=rh;a6&disin=6wi;11j&div=6v;49÷=6v;48÷ontimes=6vb;zg&divonx=6vb;zh&djcy=uq;co&dlcorn=6xq;12n&dlcrop=6x9;12a&dollar=10;6&dopf=2kph;1me&dot=k9;85&doteq=6s0;rx&doteqdot=6s1;rz&dotminus=6rc;q2&dotplus=6qc;ny&dotsquare=6u9;xm&doublebarwedge=6x2;11z&downarrow=6mr;i9&downdownarrows=6oa;lc&downharpoonleft=6o3;kv&downharpoonright=6o2;ks&drbkarow=840;182&drcorn=6xr;12p&drcrop=6x8;129&dscr=2kl5;1jx&dscy=ut;cr&dsol=8ae;1cc&dstrok=7l;4z&dtdot=6wh;11i&dtri=7gf;15j&dtrif=7ge;15h&duarr=6ph;mo&duhar=86n;1a5&dwangle=886;1b3&dzcy=v3;d0&dzigrarr=7wf;17r&eDDot=8dz;1eq&eDot=6s1;s0é=6h;3u&easter=8dq;1eg&ecaron=7v;57&ecir=6s6;sbê=6i;3v&ecolon=6s5;s9&ecy=ul;ck&edot=7r;53&ee=6kn;he&efDot=6s2;s2&efr=2ko2;1l6&eg=8ey;1g9è=6g;3t&egs=8eu;1g5&egsdot=8ew;1g7&el=8ex;1g8&elinters=73b;13e&ell=6j7;fv&els=8et;1g3&elsdot=8ev;1g6&emacr=7n;51&empty=6px;n7&emptyset=6px;n5&emptyv=6px;n6&emsp=6bn;d2&emsp13=6bo;d3&emsp14=6bp;d4&eng=97;6h&ensp=6bm;d1&eogon=7t;55&eopf=2kpi;1mf&epar=6vp;103&eparsl=89v;1c6&eplus=8dt;1ek&epsi=qd;97&epsilon=qd;96&epsiv=s5;ae&eqcirc=6s6;sa&eqcolon=6s5;s8&eqsim=6rm;qq&eqslantgtr=8eu;1g4&eqslantless=8et;1g2&equals=1p;p&equest=6sf;sj&equiv=6sh;so&equivDD=8e0;1er&eqvparsl=89x;1c8&erDot=6s3;s4&erarr=86p;1a7&escr=6jz;gs&esdot=6s0;ry&esim=6rm;qr&eta=qf;99ð=6o;41ë=6j;3w&euro=6gc;f2&excl=x;2&exist=6pv;n0&expectation=6k0;gt&exponentiale=6kn;hd&fallingdotseq=6s2;s1&fcy=uc;cb&female=7k0;163&ffilig=1dkz;1ja&fflig=1dkw;1j7&ffllig=1dl0;1jb&ffr=2ko3;1l7&filig=1dkx;1j8&fjlig=2u,2y;15&flat=7l9;16e&fllig=1dky;1j9&fltns=7g1;153&fnof=b6;7v&fopf=2kpj;1mg&forall=6ps;mt&fork=6vo;102&forkv=8gp;1in&fpartint=8b1;1cp½=59;2k&frac13=6kz;hh¼=58;2j&frac15=6l1;hj&frac16=6l5;hn&frac18=6l7;hp&frac23=6l0;hi&frac25=6l2;hk¾=5a;2m&frac35=6l3;hl&frac38=6l8;hq&frac45=6l4;hm&frac56=6l6;ho&frac58=6l9;hr&frac78=6la;hs&frasl=6dg;eq&frown=6xu;12r&fscr=2kl7;1jy&gE=6sn;t8&gEl=8ek;1ft&gacute=dx;7x&gamma=qb;94&gammad=rh;a7&gap=8ee;1fh&gbreve=7z;5b&gcirc=7x;59&gcy=tv;bu&gdot=81;5d&ge=6sl;sx&gel=6vv;10k&geq=6sl;sw&geqq=6sn;t7&geqslant=8e6;1f6&ges=8e6;1f7&gescc=8fd;1gn&gesdot=8e8;1f9&gesdoto=8ea;1fb&gesdotol=8ec;1fd&gesl=6vv,1e68;10h&gesles=8es;1g1&gfr=2ko4;1l8&gg=6sr;ts&ggg=6vt;10b&gimel=6k7;h6&gjcy=ur;cp&gl=6t3;un&glE=8eq;1fz&gla=8f9;1gj&glj=8f8;1gi&gnE=6sp;tg&gnap=8ei;1fp&gnapprox=8ei;1fo&gne=8eg;1fl&gneq=8eg;1fk&gneqq=6sp;tf&gnsim=6w7;10y&gopf=2kpk;1mh&grave=2o;14&gscr=6iy;f9&gsim=6sz;ud&gsime=8em;1fv&gsiml=8eo;1fx>=1q;s>cc=8fb;1gl>cir=8e2;1et>dot=6vr;107>lPar=87p;1aw>quest=8e4;1ev>rapprox=8ee;1fg>rarr=86w;1ad>rdot=6vr;106>reqless=6vv;10j>reqqless=8ek;1fs>rless=6t3;um>rsim=6sz;uc&gvertneqq=6sp,1e68;td&gvnE=6sp,1e68;te&hArr=6ok;m5&hairsp=6bu;da&half=59;2l&hamilt=6iz;fb&hardcy=ui;ch&harr=6ms;id&harrcir=85k;192&harrw=6nh;js&hbar=6j3;fl&hcirc=85;5g&hearts=7l1;16a&heartsuit=7l1;169&hellip=6cm;eb&hercon=6ux;yr&hfr=2ko5;1l9&hksearow=84l;18i&hkswarow=84m;18k&hoarr=6pr;mr&homtht=6rf;q5&hookleftarrow=6nd;jj&hookrightarrow=6ne;jl&hopf=2kpl;1mi&horbar=6c5;do&hscr=2kl9;1jz&hslash=6j3;fi&hstrok=87;5i&hybull=6df;ep&hyphen=6c0;dkí=6l;3y&ic=6eb;f1î=6m;3z&icy=u0;bz&iecy=tx;bw¡=4h;1f&iff=6ok;m6&ifr=2ko6;1laì=6k;3x&ii=6ko;hg&iiiint=8b0;1cn&iiint=6r1;pg&iinfin=89o;1c3&iiota=6jt;gm&ijlig=8j;5t&imacr=8b;5m&image=6j5;fp&imagline=6j4;fm&imagpart=6j5;fo&imath=8h;5r&imof=6uv;yo&imped=c5;7w&in=6q0;nd&incare=6it;f8&infin=6qm;of&infintie=89p;1c4&inodot=8h;5q&int=6qz;pe&intcal=6uy;yt&integers=6jo;gh&intercal=6uy;ys&intlarhk=8bb;1cx&intprod=8cc;1dk&iocy=up;cn&iogon=8f;5o&iopf=2kpm;1mj&iota=qh;9b&iprod=8cc;1dl¿=5b;2n&iscr=2kla;1k0&isin=6q0;nc&isinE=6wp;11r&isindot=6wl;11n&isins=6wk;11l&isinsv=6wj;11k&isinv=6q0;nb&it=6ea;ez&itilde=89;5k&iukcy=uu;csï=6n;40&jcirc=8l;5v&jcy=u1;c0&jfr=2ko7;1lb&jmath=fr;7y&jopf=2kpn;1mk&jscr=2klb;1k1&jsercy=uw;cu&jukcy=us;cq&kappa=qi;9c&kappav=s0;a9&kcedil=8n;5x&kcy=u2;c1&kfr=2ko8;1lc&kgreen=8o;5y&khcy=ud;cc&kjcy=v0;cy&kopf=2kpo;1ml&kscr=2klc;1k2&lAarr=6oq;mf&lArr=6og;ls&lAtail=84b;18a&lBarr=83y;17z&lE=6sm;t2&lEg=8ej;1fr&lHar=86a;19q&lacute=8q;60&laemptyv=88k;1bh&lagran=6j6;ft&lambda=qj;9d&lang=7vs;16z&langd=87l;1as&langle=7vs;16y&lap=8ed;1ff«=4r;1t&larr=6mo;hx&larrb=6p0;mk&larrbfs=84f;18e&larrfs=84d;18c&larrhk=6nd;jk&larrlp=6nf;jo&larrpl=855;18y&larrsim=86r;1a9&larrtl=6n6;j7&lat=8ff;1gp&latail=849;188&late=8fh;1gt&lates=8fh,1e68;1gs&lbarr=83w;17w&lbbrk=7si;16p&lbrace=3f;16&lbrack=2j;v&lbrke=87f;1am&lbrksld=87j;1aq&lbrkslu=87h;1ao&lcaron=8u;64&lcedil=8s;62&lceil=6x4;122&lcub=3f;17&lcy=u3;c2&ldca=852;18v&ldquo=6cc;dz&ldquor=6ce;e3&ldrdhar=86f;19v&ldrushar=85n;195&ldsh=6nm;jz&le=6sk;st&leftarrow=6mo;hv&leftarrowtail=6n6;j6&leftharpoondown=6nx;kd&leftharpoonup=6nw;ka&leftleftarrows=6o7;l6&leftrightarrow=6ms;ic&leftrightarrows=6o6;l4&leftrightharpoons=6ob;lf&leftrightsquigarrow=6nh;jr&leftthreetimes=6vf;zl&leg=6vu;10g&leq=6sk;ss&leqq=6sm;t1&leqslant=8e5;1f0&les=8e5;1f1&lescc=8fc;1gm&lesdot=8e7;1f8&lesdoto=8e9;1fa&lesdotor=8eb;1fc&lesg=6vu,1e68;10d&lesges=8er;1g0&lessapprox=8ed;1fe&lessdot=6vq;104&lesseqgtr=6vu;10f&lesseqqgtr=8ej;1fq&lessgtr=6t2;uj&lesssim=6sy;u9&lfisht=870;1ag&lfloor=6x6;126&lfr=2ko9;1ld&lg=6t2;uk&lgE=8ep;1fy&lhard=6nx;kf&lharu=6nw;kc&lharul=86i;19y&lhblk=7es;14o&ljcy=ux;cv&ll=6sq;tm&llarr=6o7;l7&llcorner=6xq;12m&llhard=86j;19z&lltri=7i2;15w&lmidot=8w;66&lmoust=71s;131&lmoustache=71s;130&lnE=6so;tc&lnap=8eh;1fn&lnapprox=8eh;1fm&lne=8ef;1fj&lneq=8ef;1fi&lneqq=6so;tb&lnsim=6w6;10x&loang=7vw;175&loarr=6pp;mp&lobrk=7vq;16u&longleftarrow=7w5;178&longleftrightarrow=7w7;17e&longmapsto=7wc;17p&longrightarrow=7w6;17b&looparrowleft=6nf;jn&looparrowright=6ng;jp&lopar=879;1ak&lopf=2kpp;1mm&loplus=8bx;1d6&lotimes=8c4;1dc&lowast=6qf;o5&lowbar=2n;12&loz=7gq;15p&lozenge=7gq;15o&lozf=8a3;1ca&lpar=14;b&lparlt=87n;1au&lrarr=6o6;l5&lrcorner=6xr;12o&lrhar=6ob;lg&lrhard=86l;1a1&lrm=6by;di&lrtri=6v3;yx&lsaquo=6d5;ek&lscr=2kld;1k3&lsh=6nk;jw&lsim=6sy;ua&lsime=8el;1fu&lsimg=8en;1fw&lsqb=2j;w&lsquo=6c8;ds&lsquor=6ca;dw&lstrok=8y;68<=1o;n<cc=8fa;1gk<cir=8e1;1es<dot=6vq;105<hree=6vf;zm<imes=6vd;zj<larr=86u;1ac<quest=8e3;1eu<rPar=87q;1ax<ri=7gj;15n<rie=6us;yi<rif=7gi;15l&lurdshar=85m;194&luruhar=86e;19u&lvertneqq=6so,1e68;t9&lvnE=6so,1e68;ta&mDDot=6re;q4¯=4v;20&male=7k2;164&malt=7q8;16m&maltese=7q8;16l&map=6na;jg&mapsto=6na;jf&mapstodown=6nb;ji&mapstoleft=6n8;jb&mapstoup=6n9;jd&marker=7fy;152&mcomma=8bt;1d4&mcy=u4;c3&mdash=6c4;dn&measuredangle=6qp;ok&mfr=2koa;1le&mho=6jr;gjµ=51;29&mid=6qr;oq&midast=16;d&midcir=8hc;1j1·=53;2d&minus=6qa;nu&minusb=6u7;xj&minusd=6rc;q3&minusdu=8bu;1d5&mlcp=8gr;1ip&mldr=6cm;ec&mnplus=6qb;nw&models=6uf;xy&mopf=2kpq;1mn&mp=6qb;nx&mscr=2kle;1k4&mstpos=6ri;qf&mu=qk;9e&multimap=6uw;yp&mumap=6uw;yq&nGg=6vt,mw;10a&nGt=6sr,6he;tp&nGtv=6sr,mw;to&nLeftarrow=6od;lk&nLeftrightarrow=6oe;lm&nLl=6vs,mw;108&nLt=6sq,6he;tj&nLtv=6sq,mw;ti&nRightarrow=6of;lo&nVDash=6un;y7&nVdash=6um;y6&nabla=6pz;n8&nacute=90;6a&nang=6qo,6he;oh&nap=6rt;rb&napE=8ds,mw;1ei&napid=6rv,mw;re&napos=95;6f&napprox=6rt;ra&natur=7la;16g&natural=7la;16f&naturals=6j9;fw =4g;1e&nbump=6ry,mw;rm&nbumpe=6rz,mw;rr&ncap=8cj;1dp&ncaron=94;6e&ncedil=92;6c&ncong=6rr;r2&ncongdot=8dp,mw;1ee&ncup=8ci;1do&ncy=u5;c4&ndash=6c3;dm&ne=6sg;sl&neArr=6on;mb&nearhk=84k;18h&nearr=6mv;im&nearrow=6mv;il&nedot=6s0,mw;rv&nequiv=6si;sq&nesear=84o;18n&nesim=6rm,mw;qo&nexist=6pw;n3&nexists=6pw;n2&nfr=2kob;1lf&ngE=6sn,mw;t4&nge=6sx;u7&ngeq=6sx;u6&ngeqq=6sn,mw;t5&ngeqslant=8e6,mw;1f3&nges=8e6,mw;1f4&ngsim=6t1;uh&ngt=6sv;u1&ngtr=6sv;u0&nhArr=6oe;ln&nharr=6ni;ju&nhpar=8he;1j3&ni=6q3;nk&nis=6ws;11u&nisd=6wq;11s&niv=6q3;nj&njcy=uy;cw&nlArr=6od;ll&nlE=6sm,mw;sy&nlarr=6my;iu&nldr=6cl;ea&nle=6sw;u4&nleftarrow=6my;it&nleftrightarrow=6ni;jt&nleq=6sw;u3&nleqq=6sm,mw;sz&nleqslant=8e5,mw;1ex&nles=8e5,mw;1ey&nless=6su;tx&nlsim=6t0;uf&nlt=6su;ty&nltri=6wa;115&nltrie=6wc;11b&nmid=6qs;ou&nopf=2kpr;1mo¬=4s;1u¬in=6q1;ng¬inE=6wp,mw;11q¬indot=6wl,mw;11m¬inva=6q1;nf¬invb=6wn;11p¬invc=6wm;11o¬ni=6q4;nn¬niva=6q4;nm¬nivb=6wu;11w¬nivc=6wt;11v&npar=6qu;p4&nparallel=6qu;p2&nparsl=8hp,6hx;1j5&npart=6pu,mw;mw&npolint=8b8;1cu&npr=6tc;vd&nprcue=6w0;10q&npre=8fj,mw;1gw&nprec=6tc;vc&npreceq=8fj,mw;1gx&nrArr=6of;lp&nrarr=6mz;iw&nrarrc=84z,mw;18s&nrarrw=6n1,mw;ix&nrightarrow=6mz;iv&nrtri=6wb;118&nrtrie=6wd;11e&nsc=6td;vg&nsccue=6w1;10s&nsce=8fk,mw;1h2&nscr=2klf;1k5&nshortmid=6qs;os&nshortparallel=6qu;p1&nsim=6rl;qm&nsime=6ro;qx&nsimeq=6ro;qw&nsmid=6qs;ot&nspar=6qu;p3&nsqsube=6w2;10u&nsqsupe=6w3;10w&nsub=6tg;vs&nsubE=8g5,mw;1hv&nsube=6tk;w2&nsubset=6te,6he;vi&nsubseteq=6tk;w1&nsubseteqq=8g5,mw;1hw&nsucc=6td;vf&nsucceq=8fk,mw;1h3&nsup=6th;vt&nsupE=8g6,mw;1hz&nsupe=6tl;w5&nsupset=6tf,6he;vn&nsupseteq=6tl;w4&nsupseteqq=8g6,mw;1i0&ntgl=6t5;urñ=6p;42&ntlg=6t4;up&ntriangleleft=6wa;114&ntrianglelefteq=6wc;11a&ntriangleright=6wb;117&ntrianglerighteq=6wd;11d&nu=ql;9f&num=z;5&numero=6ja;fy&numsp=6br;d5&nvDash=6ul;y5&nvHarr=83o;17u&nvap=6rx,6he;ri&nvdash=6uk;y4&nvge=6sl,6he;su&nvgt=1q,6he;q&nvinfin=89q;1c5&nvlArr=83m;17s&nvle=6sk,6he;sr&nvlt=1o,6he;l&nvltrie=6us,6he;yf&nvrArr=83n;17t&nvrtrie=6ut,6he;yj&nvsim=6rg,6he;q6&nwArr=6om;ma&nwarhk=84j;18g&nwarr=6mu;ij&nwarrow=6mu;ii&nwnear=84n;18m&oS=79k;13hó=6r;44&oast=6u3;xd&ocir=6u2;xbô=6s;45&ocy=u6;c5&odash=6u5;xf&odblac=9d;6l&odiv=8c8;1dg&odot=6u1;x9&odsold=88s;1bn&oelig=9f;6n&ofcir=88v;1bp&ofr=2koc;1lg&ogon=kb;87ò=6q;43&ogt=88x;1br&ohbar=88l;1bi&ohm=q1;91&oint=6r2;pk&olarr=6nu;k7&olcir=88u;1bo&olcross=88r;1bm&oline=6da;en&olt=88w;1bq&omacr=99;6j&omega=qx;9u&omicron=qn;9h&omid=88m;1bj&ominus=6ty;x4&oopf=2kps;1mp&opar=88n;1bk&operp=88p;1bl&oplus=6tx;x2&or=6qw;p8&orarr=6nv;k9&ord=8d9;1ea&order=6k4;h1&orderof=6k4;h0ª=4q;1sº=56;2h&origof=6uu;yn&oror=8d2;1e4&orslope=8d3;1e5&orv=8d7;1e8&oscr=6k4;h2ø=6w;4a&osol=6u0;x7õ=6t;46&otimes=6tz;x6&otimesas=8c6;1deö=6u;47&ovbar=6yl;12x&par=6qt;oz¶=52;2a¶llel=6qt;ox&parsim=8hf;1j4&parsl=8hp;1j6&part=6pu;my&pcy=u7;c6&percnt=11;7&period=1a;h&permil=6cw;ed&perp=6ud;xw&pertenk=6cx;ee&pfr=2kod;1lh&phi=qu;9r&phiv=r9;a2&phmmat=6k3;gy&phone=7im;162&pi=qo;9i&pitchfork=6vo;101&piv=ra;a4&planck=6j3;fj&planckh=6j2;fh&plankv=6j3;fk&plus=17;f&plusacir=8bn;1cz&plusb=6u6;xh&pluscir=8bm;1cy&plusdo=6qc;nz&plusdu=8bp;1d1&pluse=8du;1el±=4x;23&plussim=8bq;1d2&plustwo=8br;1d3&pm=4x;24&pointint=8b9;1cv&popf=2kpt;1mq£=4j;1h&pr=6t6;uu&prE=8fn;1h7&prap=8fr;1he&prcue=6t8;v0&pre=8fj;1h0&prec=6t6;ut&precapprox=8fr;1hd&preccurlyeq=6t8;uz&preceq=8fj;1gz&precnapprox=8ft;1hh&precneqq=8fp;1h9&precnsim=6w8;10z&precsim=6ta;v5&prime=6cy;ef&primes=6jd;g2&prnE=8fp;1ha&prnap=8ft;1hi&prnsim=6w8;110&prod=6q7;np&profalar=6y6;12v&profline=6xe;12e&profsurf=6xf;12f&prop=6ql;oe&propto=6ql;oc&prsim=6ta;v6&prurel=6uo;y8&pscr=2klh;1k6&psi=qw;9t&puncsp=6bs;d6&qfr=2koe;1li&qint=8b0;1co&qopf=2kpu;1mr&qprime=6dz;es&qscr=2kli;1k7&quaternions=6j1;ff&quatint=8ba;1cw&quest=1r;t&questeq=6sf;si"=y;4&rAarr=6or;mh&rArr=6oi;lz&rAtail=84c;18b&rBarr=83z;181&rHar=86c;19s&race=6rh,mp;qb&racute=9h;6p&radic=6qi;o8&raemptyv=88j;1bg&rang=7vt;172&rangd=87m;1at&range=885;1b2&rangle=7vt;171»=57;2i&rarr=6mq;i6&rarrap=86t;1ab&rarrb=6p1;mm&rarrbfs=84g;18f&rarrc=84z;18t&rarrfs=84e;18d&rarrhk=6ne;jm&rarrlp=6ng;jq&rarrpl=85h;191&rarrsim=86s;1aa&rarrtl=6n7;j9&rarrw=6n1;iz&ratail=84a;189&ratio=6ra;pz&rationals=6je;g4&rbarr=83x;17y&rbbrk=7sj;16q&rbrace=3h;1b&rbrack=2l;y&rbrke=87g;1an&rbrksld=87i;1ap&rbrkslu=87k;1ar&rcaron=9l;6t&rcedil=9j;6r&rceil=6x5;124&rcub=3h;1c&rcy=u8;c7&rdca=853;18w&rdldhar=86h;19x&rdquo=6cd;e2&rdquor=6cd;e1&rdsh=6nn;k0&real=6jg;g9&realine=6jf;g6&realpart=6jg;g8&reals=6jh;gc&rect=7fx;151®=4u;1y&rfisht=871;1ah&rfloor=6x7;128&rfr=2kof;1lj&rhard=6o1;kr&rharu=6o0;ko&rharul=86k;1a0&rho=qp;9j&rhov=s1;ab&rightarrow=6mq;i4&rightarrowtail=6n7;j8&rightharpoondown=6o1;kp&rightharpoonup=6o0;km&rightleftarrows=6o4;kz&rightleftharpoons=6oc;lh&rightrightarrows=6o9;la&rightsquigarrow=6n1;iy&rightthreetimes=6vg;zn&ring=ka;86&risingdotseq=6s3;s3&rlarr=6o4;l0&rlhar=6oc;lj&rlm=6bz;dj&rmoust=71t;133&rmoustache=71t;132&rnmid=8ha;1iz&roang=7vx;176&roarr=6pq;mq&robrk=7vr;16w&ropar=87a;1al&ropf=2kpv;1ms&roplus=8by;1d7&rotimes=8c5;1dd&rpar=15;c&rpargt=87o;1av&rppolint=8b6;1cs&rrarr=6o9;lb&rsaquo=6d6;el&rscr=2klj;1k8&rsh=6nl;jy&rsqb=2l;z&rsquo=6c9;dv&rsquor=6c9;du&rthree=6vg;zo&rtimes=6ve;zk&rtri=7g9;15d&rtrie=6ut;ym&rtrif=7g8;15b&rtriltri=89a;1by&ruluhar=86g;19w&rx=6ji;ge&sacute=9n;6v&sbquo=6ca;dx&sc=6t7;ux&scE=8fo;1h8&scap=8fs;1hg&scaron=9t;71&sccue=6t9;v3&sce=8fk;1h6&scedil=9r;6z&scirc=9p;6x&scnE=8fq;1hc&scnap=8fu;1hk&scnsim=6w9;112&scpolint=8b7;1ct&scsim=6tb;va&scy=u9;c8&sdot=6v9;zd&sdotb=6u9;xn&sdote=8di;1ec&seArr=6oo;mc&searhk=84l;18j&searr=6mw;ip&searrow=6mw;io§=4n;1l&semi=1n;k&seswar=84p;18p&setminus=6qe;o2&setmn=6qe;o4&sext=7qu;16n&sfr=2kog;1lk&sfrown=6xu;12q&sharp=7lb;16h&shchcy=uh;cg&shcy=ug;cf&shortmid=6qr;oo&shortparallel=6qt;ow­=4t;1v&sigma=qr;9n&sigmaf=qq;9l&sigmav=qq;9m&sim=6rg;qa&simdot=8dm;1ed&sime=6rn;qu&simeq=6rn;qt&simg=8f2;1gb&simgE=8f4;1gd&siml=8f1;1ga&simlE=8f3;1gc&simne=6rq;r0&simplus=8bo;1d0&simrarr=86q;1a8&slarr=6mo;hw&smallsetminus=6qe;o0&smashp=8c3;1db&smeparsl=89w;1c7&smid=6qr;op&smile=6xv;12t&smt=8fe;1go&smte=8fg;1gr&smtes=8fg,1e68;1gq&softcy=uk;cj&sol=1b;i&solb=890;1bu&solbar=6yn;12y&sopf=2kpw;1mt&spades=7kw;166&spadesuit=7kw;165&spar=6qt;oy&sqcap=6tv;wx&sqcaps=6tv,1e68;wv&sqcup=6tw;x0&sqcups=6tw,1e68;wy&sqsub=6tr;wk&sqsube=6tt;wr&sqsubset=6tr;wj&sqsubseteq=6tt;wq&sqsup=6ts;wo&sqsupe=6tu;wu&sqsupset=6ts;wn&sqsupseteq=6tu;wt&squ=7fl;14v&square=7fl;14u&squarf=7fu;14y&squf=7fu;14z&srarr=6mq;i5&sscr=2klk;1k9&ssetmn=6qe;o3&ssmile=6xv;12s&sstarf=6va;ze&star=7ie;161&starf=7id;160&straightepsilon=s5;ac&straightphi=r9;a0&strns=4v;1z&sub=6te;vl&subE=8g5;1hy&subdot=8fx;1hn&sube=6ti;vw&subedot=8g3;1ht&submult=8g1;1hr&subnE=8gb;1i8&subne=6tm;w9&subplus=8fz;1hp&subrarr=86x;1ae&subset=6te;vk&subseteq=6ti;vv&subseteqq=8g5;1hx&subsetneq=6tm;w8&subsetneqq=8gb;1i7&subsim=8g7;1i3&subsub=8gl;1ij&subsup=8gj;1ih&succ=6t7;uw&succapprox=8fs;1hf&succcurlyeq=6t9;v2&succeq=8fk;1h5&succnapprox=8fu;1hj&succneqq=8fq;1hb&succnsim=6w9;111&succsim=6tb;v9&sum=6q9;nt&sung=7l6;16d&sup=6tf;vr¹=55;2g²=4y;25³=4z;26&supE=8g6;1i2&supdot=8fy;1ho&supdsub=8go;1im&supe=6tj;vz&supedot=8g4;1hu&suphsol=7ux;16s&suphsub=8gn;1il&suplarr=86z;1af&supmult=8g2;1hs&supnE=8gc;1ic&supne=6tn;wd&supplus=8g0;1hq&supset=6tf;vq&supseteq=6tj;vy&supseteqq=8g6;1i1&supsetneq=6tn;wc&supsetneqq=8gc;1ib&supsim=8g8;1i4&supsub=8gk;1ii&supsup=8gm;1ik&swArr=6op;md&swarhk=84m;18l&swarr=6mx;is&swarrow=6mx;ir&swnwar=84q;18rß=67;3k&target=6xi;12h&tau=qs;9o&tbrk=71w;135&tcaron=9x;75&tcedil=9v;73&tcy=ua;c9&tdot=6hn;f4&telrec=6xh;12g&tfr=2koh;1ll&there4=6r8;pv&therefore=6r8;pu&theta=qg;9a&thetasym=r5;9v&thetav=r5;9x&thickapprox=6rs;r3&thicksim=6rg;q7&thinsp=6bt;d8&thkap=6rs;r7&thksim=6rg;q8þ=72;4g&tilde=kc;89×=5z;3c×b=6u8;xl×bar=8c1;1da×d=8c0;1d9&tint=6r1;ph&toea=84o;18o&top=6uc;xt&topbot=6ye;12w&topcir=8hd;1j2&topf=2kpx;1mu&topfork=8gq;1io&tosa=84p;18q&tprime=6d0;eh&trade=6jm;gg&triangle=7g5;158&triangledown=7gf;15i&triangleleft=7gj;15m&trianglelefteq=6us;yh&triangleq=6sc;sg&triangleright=7g9;15c&trianglerighteq=6ut;yl&tridot=7ho;15r&trie=6sc;sh&triminus=8ca;1di&triplus=8c9;1dh&trisb=899;1bx&tritime=8cb;1dj&trpezium=736;13d&tscr=2kll;1ka&tscy=ue;cd&tshcy=uz;cx&tstrok=9z;77&twixt=6ss;tu&twoheadleftarrow=6n2;j0&twoheadrightarrow=6n4;j3&uArr=6oh;lv&uHar=86b;19rú=6y;4c&uarr=6mp;i1&ubrcy=v2;cz&ubreve=a5;7dû=6z;4d&ucy=ub;ca&udarr=6o5;l2&udblac=a9;7h&udhar=86m;1a3&ufisht=872;1ai&ufr=2koi;1lmù=6x;4b&uharl=6nz;kl&uharr=6ny;ki&uhblk=7eo;14n&ulcorn=6xo;12j&ulcorner=6xo;12i&ulcrop=6xb;12c&ultri=7i0;15u&umacr=a3;7b¨=4o;1p&uogon=ab;7j&uopf=2kpy;1mv&uparrow=6mp;i0&updownarrow=6mt;if&upharpoonleft=6nz;kj&upharpoonright=6ny;kg&uplus=6tq;wg&upsi=qt;9q&upsih=r6;9y&upsilon=qt;9p&upuparrows=6o8;l8&urcorn=6xp;12l&urcorner=6xp;12k&urcrop=6xa;12b&uring=a7;7f&urtri=7i1;15v&uscr=2klm;1kb&utdot=6wg;11h&utilde=a1;79&utri=7g5;159&utrif=7g4;157&uuarr=6o8;l9ü=70;4e&uwangle=887;1b4&vArr=6ol;m9&vBar=8h4;1iu&vBarv=8h5;1iv&vDash=6ug;y0&vangrt=87w;1az&varepsilon=s5;ad&varkappa=s0;a8&varnothing=6px;n4&varphi=r9;a1&varpi=ra;a3&varpropto=6ql;ob&varr=6mt;ig&varrho=s1;aa&varsigma=qq;9k&varsubsetneq=6tm,1e68;w6&varsubsetneqq=8gb,1e68;1i5&varsupsetneq=6tn,1e68;wa&varsupsetneqq=8gc,1e68;1i9&vartheta=r5;9w&vartriangleleft=6uq;y9&vartriangleright=6ur;yc&vcy=tu;bt&vdash=6ua;xp&vee=6qw;p7&veebar=6uz;yu&veeeq=6sa;sf&vellip=6we;11f&verbar=3g;19&vert=3g;1a&vfr=2koj;1ln&vltri=6uq;yb&vnsub=6te,6he;vj&vnsup=6tf,6he;vo&vopf=2kpz;1mw&vprop=6ql;od&vrtri=6ur;ye&vscr=2kln;1kc&vsubnE=8gb,1e68;1i6&vsubne=6tm,1e68;w7&vsupnE=8gc,1e68;1ia&vsupne=6tn,1e68;wb&vzigzag=87u;1ay&wcirc=ad;7l&wedbar=8db;1eb&wedge=6qv;p5&wedgeq=6s9;se&weierp=6jc;g0&wfr=2kok;1lo&wopf=2kq0;1mx&wp=6jc;g1&wr=6rk;qk&wreath=6rk;qj&wscr=2klo;1kd&xcap=6v6;z6&xcirc=7hr;15t&xcup=6v7;z9&xdtri=7gd;15f&xfr=2kol;1lp&xhArr=7wa;17o&xharr=7w7;17f&xi=qm;9g&xlArr=7w8;17i&xlarr=7w5;179&xmap=7wc;17q&xnis=6wr;11t&xodot=8ao;1ce&xopf=2kq1;1my&xoplus=8ap;1cg&xotime=8aq;1ci&xrArr=7w9;17l&xrarr=7w6;17c&xscr=2klp;1ke&xsqcup=8au;1cm&xuplus=8as;1ck&xutri=7g3;155&xvee=6v5;z2&xwedge=6v4;yzý=71;4f&yacy=un;cm&ycirc=af;7n&ycy=uj;ci¥=4l;1j&yfr=2kom;1lq&yicy=uv;ct&yopf=2kq2;1mz&yscr=2klq;1kf&yucy=um;clÿ=73;4h&zacute=ai;7q&zcaron=am;7u&zcy=tz;by&zdot=ak;7s&zeetrf=6js;gk&zeta=qe;98&zfr=2kon;1lr&zhcy=ty;bx&zigrarr=6ot;mi&zopf=2kq3;1n0&zscr=2klr;1kg&zwj=6bx;dh&zwnj=6bw;dg&"; + } } From 8e3eeb5f41a21b57c407b24b4e9c5ee036847f1b Mon Sep 17 00:00:00 2001 From: offa Date: Sun, 2 Jun 2019 21:50:06 +0000 Subject: [PATCH 325/774] Jdk11 / Jdk12 JavaDoc generation fix (#1217) * JavaDoc generation fixed for JDK11 and JDK12 (JDK-8212233). * Maven-javadoc-plugin updated and doclint option changed to new one (since v3.0.0). --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 91e76a41e0..d6729bf5d2 100644 --- a/pom.xml +++ b/pom.xml @@ -73,9 +73,10 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.0-M1 + 3.1.0 - -Xdoclint:none + none + 7 From a4b5e826f2440b19ee2a98fdece7211433cfdaed Mon Sep 17 00:00:00 2001 From: offa Date: Sun, 2 Jun 2019 21:51:32 +0000 Subject: [PATCH 326/774] Code improvements (#1216) * Code simplified. * Code improvements: - String concatenation avoided - Size checks replaced by isEmpty() - Initialization fixed - Missing Overrides added --- src/main/java/org/jsoup/helper/DataUtil.java | 3 +- .../java/org/jsoup/helper/HttpConnection.java | 2 +- .../org/jsoup/parser/HtmlTreeBuilder.java | 4 +- src/main/java/org/jsoup/safety/Cleaner.java | 4 +- src/main/java/org/jsoup/select/Evaluator.java | 52 +++++++++---------- src/test/java/org/jsoup/MultiLocaleRule.java | 1 + src/test/java/org/jsoup/TextUtil.java | 3 +- src/test/java/org/jsoup/nodes/NodeTest.java | 20 +++---- .../org/jsoup/parser/AttributeParseTest.java | 20 +++---- .../java/org/jsoup/parser/TokeniserTest.java | 2 +- .../java/org/jsoup/select/ElementsTest.java | 36 +++++++------ .../java/org/jsoup/select/TraversorTest.java | 26 +++++++--- 12 files changed, 93 insertions(+), 80 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 49b7f09b17..bd4ec6c701 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -99,12 +99,11 @@ static Document parseInputStream(InputStream input, String charsetName, String b input = ConstrainableInputStream.wrap(input, bufferSize, 0); Document doc = null; - boolean fullyRead = false; // read the start of the stream and look for a BOM or meta charset input.mark(bufferSize); ByteBuffer firstBytes = readToByteBuffer(input, firstReadBufferSize - 1); // -1 because we read one more to see if completed. First read is < buffer size, so can't be invalid. - fullyRead = input.read() == -1; + boolean fullyRead = (input.read() == -1); input.reset(); // look for BOM - overrides any other header or input diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index f834b1a6c4..4063130d72 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -446,7 +446,7 @@ public T header(String name, String value) { public boolean hasHeader(String name) { Validate.notEmpty(name, "Header name must not be empty"); - return getHeadersCaseInsensitive(name).size() != 0; + return !getHeadersCaseInsensitive(name).isEmpty(); } /** diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index bea598dea6..fd7d2bfbbf 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -204,7 +204,7 @@ Element insert(Token.StartTag startTag) { tokeniser.emit(emptyEnd.reset().name(el.tagName())); // ensure we get out of whatever state we are in. emitted for yielded processing return el; } - + Element el = new Element(Tag.valueOf(startTag.name(), settings), baseUri, settings.normalizeAttributes(startTag.attributes)); insert(el); return el; @@ -268,7 +268,7 @@ else if (tagName.equals("script") || tagName.equals("style")) private void insertNode(Node node) { // if the stack hasn't been set up yet, elements (doctype, comments) go into the doc - if (stack.size() == 0) + if (stack.isEmpty()) doc.appendChild(node); else if (isFosterInserts()) insertInFosterParent(node); diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 0ac5edb0f9..0d3c7e9fcb 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -77,7 +77,7 @@ public boolean isValid(Document dirtyDocument) { Document clean = Document.createShell(dirtyDocument.baseUri()); int numDiscarded = copySafeNodes(dirtyDocument.body(), clean.body()); return numDiscarded == 0 - && dirtyDocument.head().childNodes().size() == 0; // because we only look at the body, but we start from a shell, make sure there's nothing in the head + && dirtyDocument.head().childNodes().isEmpty(); // because we only look at the body, but we start from a shell, make sure there's nothing in the head } public boolean isValidBodyHtml(String bodyHtml) { @@ -87,7 +87,7 @@ public boolean isValidBodyHtml(String bodyHtml) { List nodes = Parser.parseFragment(bodyHtml, dirty.body(), "", errorList); dirty.body().insertChildren(0, nodes); int numDiscarded = copySafeNodes(dirty.body(), clean.body()); - return numDiscarded == 0 && errorList.size() == 0; + return numDiscarded == 0 && errorList.isEmpty(); } /** diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 735606f22a..0177d739f3 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -391,7 +391,7 @@ public String toString() { } } - + /** * Evaluator for matching the last sibling (css :last-child) */ @@ -401,13 +401,13 @@ public boolean matches(Element root, Element element) { final Element p = element.parent(); return p != null && !(p instanceof Document) && element.elementSiblingIndex() == p.children().size()-1; } - + @Override public String toString() { return ":last-child"; } } - + public static final class IsFirstOfType extends IsNthOfType { public IsFirstOfType() { super(0,1); @@ -417,7 +417,7 @@ public String toString() { return ":first-of-type"; } } - + public static final class IsLastOfType extends IsNthLastOfType { public IsLastOfType() { super(0,1); @@ -428,10 +428,10 @@ public String toString() { } } - + public static abstract class CssNthEvaluator extends Evaluator { protected final int a, b; - + public CssNthEvaluator(int a, int b) { this.a = a; this.b = b; @@ -439,18 +439,18 @@ public CssNthEvaluator(int a, int b) { public CssNthEvaluator(int b) { this(0,b); } - + @Override public boolean matches(Element root, Element element) { final Element p = element.parent(); if (p == null || (p instanceof Document)) return false; - + final int pos = calculatePosition(root, element); if (a == 0) return pos == b; - + return (pos-b)*a >= 0 && (pos-b)%a==0; } - + @Override public String toString() { if (a == 0) @@ -459,15 +459,15 @@ public String toString() { return String.format(":%s(%dn)",getPseudoClass(), a); return String.format(":%s(%dn%+d)", getPseudoClass(),a, b); } - + protected abstract String getPseudoClass(); protected abstract int calculatePosition(Element root, Element element); } - - + + /** * css-compatible Evaluator for :eq (css :nth-child) - * + * * @see IndexEquals */ public static final class IsNthChild extends CssNthEvaluator { @@ -480,15 +480,15 @@ protected int calculatePosition(Element root, Element element) { return element.elementSiblingIndex()+1; } - + protected String getPseudoClass() { return "nth-child"; } } - + /** * css pseudo class :nth-last-child) - * + * * @see IndexEquals */ public static final class IsNthLastChild extends CssNthEvaluator { @@ -500,16 +500,16 @@ public IsNthLastChild(int a, int b) { protected int calculatePosition(Element root, Element element) { return element.parent().children().size() - element.elementSiblingIndex(); } - + @Override protected String getPseudoClass() { return "nth-last-child"; } } - + /** * css pseudo class nth-of-type - * + * */ public static class IsNthOfType extends CssNthEvaluator { public IsNthOfType(int a, int b) { @@ -531,13 +531,13 @@ protected String getPseudoClass() { return "nth-of-type"; } } - + public static class IsNthLastOfType extends CssNthEvaluator { public IsNthLastOfType(int a, int b) { super(a, b); } - + @Override protected int calculatePosition(Element root, Element element) { int pos = 0; @@ -563,13 +563,13 @@ public boolean matches(Element root, Element element) { final Element p = element.parent(); return p != null && !(p instanceof Document) && element.elementSiblingIndex() == 0; } - + @Override public String toString() { return ":first-child"; } } - + /** * css3 pseudo-class :root * @see :root selector @@ -591,7 +591,7 @@ public static final class IsOnlyChild extends Evaluator { @Override public boolean matches(Element root, Element element) { final Element p = element.parent(); - return p!=null && !(p instanceof Document) && element.siblingElements().size() == 0; + return p!=null && !(p instanceof Document) && element.siblingElements().isEmpty(); } @Override public String toString() { @@ -604,7 +604,7 @@ public static final class IsOnlyOfType extends Evaluator { public boolean matches(Element root, Element element) { final Element p = element.parent(); if (p==null || p instanceof Document) return false; - + int pos = 0; Elements family = p.children(); for (Element el : family) { diff --git a/src/test/java/org/jsoup/MultiLocaleRule.java b/src/test/java/org/jsoup/MultiLocaleRule.java index 3a0ee98bef..4e63f09cc0 100644 --- a/src/test/java/org/jsoup/MultiLocaleRule.java +++ b/src/test/java/org/jsoup/MultiLocaleRule.java @@ -13,6 +13,7 @@ public class MultiLocaleRule implements TestRule { public @interface MultiLocaleTest { } + @Override public Statement apply(final Statement statement, final Description description) { return new Statement() { @Override diff --git a/src/test/java/org/jsoup/TextUtil.java b/src/test/java/org/jsoup/TextUtil.java index 922f4c77d1..39ff7cdc79 100644 --- a/src/test/java/org/jsoup/TextUtil.java +++ b/src/test/java/org/jsoup/TextUtil.java @@ -6,7 +6,6 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextUtil { public static String stripNewlines(String text) { - text = text.replaceAll("\\r?\\n\\s*", ""); - return text; + return text.replaceAll("\\r?\\n\\s*", ""); } } diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 5e18338b08..722e4c46e8 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -31,14 +31,14 @@ public class NodeTest { Element dodgyBase = new Element(tag, "wtf://no-such-protocol/", attribs); assertEquals("http://bar/qux", dodgyBase.absUrl("absHref")); // base fails, but href good, so get that - assertEquals("", dodgyBase.absUrl("relHref")); // base fails, only rel href, so return nothing + assertEquals("", dodgyBase.absUrl("relHref")); // base fails, only rel href, so return nothing } @Test public void setBaseUriIsRecursive() { Document doc = Jsoup.parse("

    "); String baseUri = "https://jsoup.org"; doc.setBaseUri(baseUri); - + assertEquals(baseUri, doc.baseUri()); assertEquals(baseUri, doc.select("div").first().baseUri()); assertEquals(baseUri, doc.select("p").first().baseUri()); @@ -130,25 +130,25 @@ public void handlesAbsOnProtocolessAbsoluteUris() { Element a1 = doc.select("a").first(); assertEquals("http://example.com/one/two.html", a1.absUrl("href")); } - + @Test public void testRemove() { Document doc = Jsoup.parse("

    One two three

    "); Element p = doc.select("p").first(); p.childNode(0).remove(); - + assertEquals("two three", p.text()); assertEquals("two three", TextUtil.stripNewlines(p.html())); } - + @Test public void testReplace() { Document doc = Jsoup.parse("

    One two three

    "); Element p = doc.select("p").first(); Element insert = doc.createElement("em").text("foo"); p.childNode(1).replaceWith(insert); - + assertEquals("One foo three", p.html()); } - + @Test public void ownerDocument() { Document doc = Jsoup.parse("

    Hello"); Element p = doc.select("p").first(); @@ -221,12 +221,14 @@ public void handlesAbsOnProtocolessAbsoluteUris() { Document doc = Jsoup.parse("

    Hello

    There
    "); final StringBuilder accum = new StringBuilder(); doc.select("div").first().traverse(new NodeVisitor() { + @Override public void head(Node node, int depth) { - accum.append("<" + node.nodeName() + ">"); + accum.append("<").append(node.nodeName()).append(">"); } + @Override public void tail(Node node, int depth) { - accum.append(""); + accum.append(""); } }); assertEquals("

    <#text>

    ", accum.toString()); diff --git a/src/test/java/org/jsoup/parser/AttributeParseTest.java b/src/test/java/org/jsoup/parser/AttributeParseTest.java index 956220f970..c6db61837a 100644 --- a/src/test/java/org/jsoup/parser/AttributeParseTest.java +++ b/src/test/java/org/jsoup/parser/AttributeParseTest.java @@ -71,30 +71,30 @@ public class AttributeParseTest { Elements els = Jsoup.parse(html).select("a"); assertEquals("&wr_id=123&mid-size=true&ok=&wr", els.first().attr("href")); } - + @Test public void parsesBooleanAttributes() { String html = ""; Element el = Jsoup.parse(html).select("a").first(); - + assertEquals("123", el.attr("normal")); assertEquals("", el.attr("boolean")); assertEquals("", el.attr("empty")); - + List attributes = el.attributes().asList(); assertEquals("There should be 3 attribute present", 3, attributes.size()); - + // Assuming the list order always follows the parsed html - assertFalse("'normal' attribute should not be boolean", attributes.get(0) instanceof BooleanAttribute); - assertTrue("'boolean' attribute should be boolean", attributes.get(1) instanceof BooleanAttribute); - assertFalse("'empty' attribute should not be boolean", attributes.get(2) instanceof BooleanAttribute); - + assertFalse("'normal' attribute should not be boolean", attributes.get(0) instanceof BooleanAttribute); + assertTrue("'boolean' attribute should be boolean", attributes.get(1) instanceof BooleanAttribute); + assertFalse("'empty' attribute should not be boolean", attributes.get(2) instanceof BooleanAttribute); + assertEquals(html, el.outerHtml()); } - + @Test public void dropsSlashFromAttributeName() { String html = ""; Document doc = Jsoup.parse(html); - assertTrue("SelfClosingStartTag ignores last character", doc.select("img[onerror]").size() != 0); + assertTrue("SelfClosingStartTag ignores last character", !doc.select("img[onerror]").isEmpty()); assertEquals("", doc.body().html()); doc = Jsoup.parse(html, "", Parser.xmlParser()); diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index 52aba9dec8..2a2aa57401 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -33,7 +33,7 @@ public void bufferUpInAttributeVal() { } sb.append('X'); // First character to cross character buffer boundary - sb.append(tail + quote + ">\n"); + sb.append(tail).append(quote).append(">\n"); String html = sb.toString(); Document doc = Jsoup.parse(html); diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index 6fbec69a3a..d54c4d0798 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -46,7 +46,7 @@ public class ElementsTest { assertEquals("classy", ps.last().attr("style")); assertEquals("bar", ps.last().attr("class")); } - + @Test public void hasAttr() { Document doc = Jsoup.parse("

    "); Elements ps = doc.select("p"); @@ -63,7 +63,7 @@ public class ElementsTest { assertTrue(two.hasAttr("abs:href")); assertTrue(both.hasAttr("abs:href")); // hits on #2 } - + @Test public void attr() { Document doc = Jsoup.parse("

    "); String classVal = doc.select("p").attr("class"); @@ -110,7 +110,7 @@ public class ElementsTest { assertTrue(thr.hasClass("ThreE")); assertTrue(thr.hasClass("three")); } - + @Test public void text() { String h = "

    Hello

    there

    world

    "; Document doc = Jsoup.parse(h); @@ -123,49 +123,49 @@ public class ElementsTest { assertTrue(divs.hasText()); assertFalse(doc.select("div + div").hasText()); } - + @Test public void html() { Document doc = Jsoup.parse("

    Hello

    There

    "); Elements divs = doc.select("div"); assertEquals("

    Hello

    \n

    There

    ", divs.html()); } - + @Test public void outerHtml() { Document doc = Jsoup.parse("

    Hello

    There

    "); Elements divs = doc.select("div"); assertEquals("

    Hello

    There

    ", TextUtil.stripNewlines(divs.outerHtml())); } - + @Test public void setHtml() { Document doc = Jsoup.parse("

    One

    Two

    Three

    "); Elements ps = doc.select("p"); - + ps.prepend("Bold").append("Ital"); assertEquals("

    BoldTwoItal

    ", TextUtil.stripNewlines(ps.get(1).outerHtml())); - + ps.html("Gone"); assertEquals("

    Gone

    ", TextUtil.stripNewlines(ps.get(1).outerHtml())); } - + @Test public void val() { Document doc = Jsoup.parse(""); Elements els = doc.select("input, textarea"); assertEquals(2, els.size()); assertEquals("one", els.val()); assertEquals("two", els.last().val()); - + els.val("three"); assertEquals("three", els.first().val()); assertEquals("three", els.last().val()); assertEquals("", els.last().outerHtml()); } - + @Test public void before() { Document doc = Jsoup.parse("

    This is jsoup.

    "); doc.select("a").before("foo"); assertEquals("

    This foois foojsoup.

    ", TextUtil.stripNewlines(doc.body().html())); } - + @Test public void after() { Document doc = Jsoup.parse("

    This is jsoup.

    "); doc.select("a").after("foo"); @@ -219,18 +219,18 @@ public class ElementsTest { @Test public void remove() { Document doc = Jsoup.parse("

    Hello there

    jsoup

    now!

    "); doc.outputSettings().prettyPrint(false); - + doc.select("p").remove(); assertEquals("
    jsoup
    ", doc.body().html()); } - + @Test public void eq() { String h = "

    Hello

    there

    world"; Document doc = Jsoup.parse(h); assertEquals("there", doc.select("p").eq(1).text()); assertEquals("there", doc.select("p").get(1).text()); } - + @Test public void is() { String h = "

    Hello

    there

    world"; Document doc = Jsoup.parse(h); @@ -272,12 +272,14 @@ public class ElementsTest { Document doc = Jsoup.parse("

    Hello

    There
    "); final StringBuilder accum = new StringBuilder(); doc.select("div").traverse(new NodeVisitor() { + @Override public void head(Node node, int depth) { - accum.append("<" + node.nodeName() + ">"); + accum.append("<").append(node.nodeName()).append(">"); } + @Override public void tail(Node node, int depth) { - accum.append(""); + accum.append(""); } }); assertEquals("

    <#text>

    <#text>
    ", accum.toString()); diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java index 53b52198e1..c8af364847 100644 --- a/src/test/java/org/jsoup/select/TraversorTest.java +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -16,13 +16,15 @@ public void filterVisit() { Document doc = Jsoup.parse("

    Hello

    There
    "); final StringBuilder accum = new StringBuilder(); NodeTraversor.filter(new NodeFilter() { + @Override public FilterResult head(Node node, int depth) { - accum.append("<" + node.nodeName() + ">"); + accum.append("<").append(node.nodeName()).append(">"); return FilterResult.CONTINUE; } + @Override public FilterResult tail(Node node, int depth) { - accum.append(""); + accum.append(""); return FilterResult.CONTINUE; } }, doc.select("div")); @@ -34,14 +36,16 @@ public void filterSkipChildren() { Document doc = Jsoup.parse("

    Hello

    There
    "); final StringBuilder accum = new StringBuilder(); NodeTraversor.filter(new NodeFilter() { + @Override public FilterResult head(Node node, int depth) { - accum.append("<" + node.nodeName() + ">"); + accum.append("<").append(node.nodeName()).append(">"); // OMIT contents of p: return ("p".equals(node.nodeName())) ? FilterResult.SKIP_CHILDREN : FilterResult.CONTINUE; } + @Override public FilterResult tail(Node node, int depth) { - accum.append(""); + accum.append(""); return FilterResult.CONTINUE; } }, doc.select("div")); @@ -53,16 +57,18 @@ public void filterSkipEntirely() { Document doc = Jsoup.parse("

    Hello

    There
    "); final StringBuilder accum = new StringBuilder(); NodeTraversor.filter(new NodeFilter() { + @Override public FilterResult head(Node node, int depth) { // OMIT p: if ("p".equals(node.nodeName())) return FilterResult.SKIP_ENTIRELY; - accum.append("<" + node.nodeName() + ">"); + accum.append("<").append(node.nodeName()).append(">"); return FilterResult.CONTINUE; } + @Override public FilterResult tail(Node node, int depth) { - accum.append(""); + accum.append(""); return FilterResult.CONTINUE; } }, doc.select("div")); @@ -73,11 +79,13 @@ public FilterResult tail(Node node, int depth) { public void filterRemove() { Document doc = Jsoup.parse("

    Hello

    There be bold
    "); NodeTraversor.filter(new NodeFilter() { + @Override public FilterResult head(Node node, int depth) { // Delete "p" in head: return ("p".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE; } + @Override public FilterResult tail(Node node, int depth) { // Delete "b" in tail: return ("b".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE; @@ -91,13 +99,15 @@ public void filterStop() { Document doc = Jsoup.parse("

    Hello

    There
    "); final StringBuilder accum = new StringBuilder(); NodeTraversor.filter(new NodeFilter() { + @Override public FilterResult head(Node node, int depth) { - accum.append("<" + node.nodeName() + ">"); + accum.append("<").append(node.nodeName()).append(">"); return FilterResult.CONTINUE; } + @Override public FilterResult tail(Node node, int depth) { - accum.append(""); + accum.append(""); // Stop after p. return ("p".equals(node.nodeName())) ? FilterResult.STOP : FilterResult.CONTINUE; } From e42ca3b8e00d611b5d3c0bb15ddff7163dd0f36a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Jun 2019 16:11:58 -0700 Subject: [PATCH 327/774] Move integ tests to Maven Failsafe --- CHANGES | 2 + pom.xml | 47 ++++++- .../java/org/jsoup/integration/ConnectIT.java | 115 ++++++++++++++++++ .../org/jsoup/integration/ConnectTest.java | 96 --------------- 4 files changed, 158 insertions(+), 102 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/ConnectIT.java diff --git a/CHANGES b/CHANGES index 1e77001205..d50beefb23 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ jsoup changelog * Improvement: added Element chaining methods for various overridden methods on Node. + * Various code hygiene updates. + **** Release 1.12.1 [2019-May12] * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). diff --git a/pom.xml b/pom.xml index d6729bf5d2..a2ec63cac6 100644 --- a/pom.xml +++ b/pom.xml @@ -93,9 +93,9 @@ maven-source-plugin 3.0.1 - - org/jsoup/examples/** - + + org/jsoup/examples/** + @@ -118,9 +118,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - org/jsoup/examples/** - + + org/jsoup/examples/** + @@ -151,6 +151,22 @@ maven-release-plugin 2.5.3 + + maven-failsafe-plugin + 3.0.0-M3 + + + + integration-test + verify + + + + + methods + 8 + + @@ -212,6 +228,25 @@ + + failsafe + + + + maven-failsafe-plugin + 2.22.0 + + + + integration-test + verify + + + + + + + diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java new file mode 100644 index 0000000000..9846095e42 --- /dev/null +++ b/src/test/java/org/jsoup/integration/ConnectIT.java @@ -0,0 +1,115 @@ +package org.jsoup.integration; + +import org.jsoup.Connection; +import org.jsoup.Jsoup; +import org.jsoup.integration.servlets.SlowRider; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.junit.Test; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Failsafe integration tests for Connect methods. These take a bit longer to run, so included as Integ, not Unit, tests. + */ +public class ConnectIT { + // Slow Rider tests. Ignored by default so tests don't take aaages + @Test + public void canInterruptBodyStringRead() throws IOException, InterruptedException { + // todo - implement in interruptable channels, so it's immediate + final String[] body = new String[1]; + Thread runner = new Thread(new Runnable() { + public void run() { + try { + Connection.Response res = Jsoup.connect(SlowRider.Url) + .timeout(15 * 1000) + .execute(); + body[0] = res.body(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + }); + + runner.start(); + Thread.sleep(1000 * 3); + runner.interrupt(); + assertTrue(runner.isInterrupted()); + runner.join(); + + assertTrue(body[0].length() > 0); + assertTrue(body[0].contains("

    Are you still there?")); + } + + @Test + public void canInterruptDocumentRead() throws IOException, InterruptedException { + // todo - implement in interruptable channels, so it's immediate + final String[] body = new String[1]; + Thread runner = new Thread(new Runnable() { + public void run() { + try { + Connection.Response res = Jsoup.connect(SlowRider.Url) + .timeout(15 * 1000) + .execute(); + body[0] = res.parse().text(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + }); + + runner.start(); + Thread.sleep(1000 * 3); + runner.interrupt(); + assertTrue(runner.isInterrupted()); + runner.join(); + + assertTrue(body[0].length() == 0); // doesn't ready a failed doc + } + + @Test + public void totalTimeout() throws IOException { + int timeout = 3 * 1000; + long start = System.currentTimeMillis(); + boolean threw = false; + try { + Jsoup.connect(SlowRider.Url).timeout(timeout).get(); + } catch (SocketTimeoutException e) { + long end = System.currentTimeMillis(); + long took = end - start; + assertTrue(("Time taken was " + took), took > timeout); + assertTrue(("Time taken was " + took), took < timeout * 1.2); + threw = true; + } + + assertTrue(threw); + } + + @Test + public void slowReadOk() throws IOException { + // make sure that a slow read that is under the request timeout is still OK + Document doc = Jsoup.connect(SlowRider.Url) + .data(SlowRider.MaxTimeParam, "2000") // the request completes in 2 seconds + .get(); + + Element h1 = doc.selectFirst("h1"); + assertEquals("outatime", h1.text()); + } + + @Test + public void infiniteReadSupported() throws IOException { + Document doc = Jsoup.connect(SlowRider.Url) + .timeout(0) + .data(SlowRider.MaxTimeParam, "2000") + .get(); + + Element h1 = doc.selectFirst("h1"); + assertEquals("outatime", h1.text()); + } +} diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 976019730e..51f1a2a381 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -233,102 +233,6 @@ public void doesPut() throws IOException { assertEquals("auth=token", ihVal("Cookie", doc)); } - // Slow Rider tests. Ignored by default so tests don't take aaages - @Ignore - @Test public void canInterruptBodyStringRead() throws IOException, InterruptedException { - // todo - implement in interruptable channels, so it's immediate - final String[] body = new String[1]; - Thread runner = new Thread(new Runnable() { - public void run() { - try { - Connection.Response res = Jsoup.connect(SlowRider.Url) - .timeout(15 * 1000) - .execute(); - body[0] = res.body(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - }); - - runner.start(); - Thread.sleep(1000 * 3); - runner.interrupt(); - assertTrue(runner.isInterrupted()); - runner.join(); - - assertTrue(body[0].length() > 0); - assertTrue(body[0].contains("

    Are you still there?")); - } - - @Ignore - @Test public void canInterruptDocumentRead() throws IOException, InterruptedException { - // todo - implement in interruptable channels, so it's immediate - final String[] body = new String[1]; - Thread runner = new Thread(new Runnable() { - public void run() { - try { - Connection.Response res = Jsoup.connect(SlowRider.Url) - .timeout(15 * 1000) - .execute(); - body[0] = res.parse().text(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - }); - - runner.start(); - Thread.sleep(1000 * 3); - runner.interrupt(); - assertTrue(runner.isInterrupted()); - runner.join(); - - assertTrue(body[0].length() == 0); // doesn't ready a failed doc - } - - @Ignore - @Test public void totalTimeout() throws IOException { - int timeout = 3 * 1000; - long start = System.currentTimeMillis(); - boolean threw = false; - try { - Jsoup.connect(SlowRider.Url).timeout(timeout).get(); - } catch (SocketTimeoutException e) { - long end = System.currentTimeMillis(); - long took = end - start; - assertTrue(("Time taken was " + took), took > timeout); - assertTrue(("Time taken was " + took), took < timeout * 1.2); - threw = true; - } - - assertTrue(threw); - } - - @Ignore - @Test public void slowReadOk() throws IOException { - // make sure that a slow read that is under the request timeout is still OK - Document doc = Jsoup.connect(SlowRider.Url) - .data(SlowRider.MaxTimeParam, "2000") // the request completes in 2 seconds - .get(); - - Element h1 = doc.selectFirst("h1"); - assertEquals("outatime", h1.text()); - } - - @Ignore - @Test public void infiniteReadSupported() throws IOException { - Document doc = Jsoup.connect(SlowRider.Url) - .timeout(0) - .data(SlowRider.MaxTimeParam, "2000") - .get(); - - Element h1 = doc.selectFirst("h1"); - assertEquals("outatime", h1.text()); - } - /** * Tests upload of content to a remote service. */ From 333fcc3f8df3ce8300730a08c012a1d0e1cf6b5c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Jun 2019 16:37:51 -0700 Subject: [PATCH 328/774] Travis fixup --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index bacde7d9ce..11a4a2602e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,15 @@ language: java +dist: xenial jdk: - - openjdk7 - - oraclejdk8 - - oraclejdk9 + - openjdk9 - openjdk10 - - oraclejdk11 + - openjdk11 - openjdk12 cache: directories: - $HOME/.m2 + +script: + - mvn verify -B From 92392a674e3905c85f960696d99fe1f3e0c19413 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 29 Jun 2019 19:50:10 -0700 Subject: [PATCH 329/774] Don't recompile regex on each hit --- src/test/java/org/jsoup/TextUtil.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/TextUtil.java b/src/test/java/org/jsoup/TextUtil.java index 39ff7cdc79..2dac4b6d6d 100644 --- a/src/test/java/org/jsoup/TextUtil.java +++ b/src/test/java/org/jsoup/TextUtil.java @@ -1,11 +1,15 @@ package org.jsoup; +import java.util.regex.Pattern; + /** Text utils to ease testing @author Jonathan Hedley, jonathan@hedley.net */ public class TextUtil { + static Pattern stripper = Pattern.compile("\\r?\\n\\s*"); + public static String stripNewlines(String text) { - return text.replaceAll("\\r?\\n\\s*", ""); + return stripper.matcher(text).replaceAll(""); } } From 27a445b029b02bced263a0686f40a4f373827953 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 4 Jul 2019 12:49:50 -0700 Subject: [PATCH 330/774] Get location from path info --- .../java/org/jsoup/integration/ConnectTest.java | 14 +++++--------- .../java/org/jsoup/integration/TestServer.java | 2 +- .../jsoup/integration/servlets/FileServlet.java | 8 ++++++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 51f1a2a381..edc5c3f115 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -415,7 +415,7 @@ public void handlesEmtpyStreamDuringBufferedRead() throws IOException { } @Test public void getUtf8Bom() throws IOException { - Connection con = Jsoup.connect(FileServlet.Url); + Connection con = Jsoup.connect(FileServlet.urlTo("/bomtests/bom_utf8.html")); con.data(FileServlet.LocationParam, "/bomtests/bom_utf8.html"); Document doc = con.get(); @@ -425,8 +425,7 @@ public void handlesEmtpyStreamDuringBufferedRead() throws IOException { @Test public void testBinaryThrowsExceptionWhenTypeIgnored() { - Connection con = Jsoup.connect(FileServlet.Url); - con.data(FileServlet.LocationParam, "/htmltests/thumb.jpg"); + Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")); con.data(FileServlet.ContentTypeParam, "image/jpeg"); con.ignoreContentType(true); @@ -443,8 +442,7 @@ public void testBinaryThrowsExceptionWhenTypeIgnored() { @Test public void testBinaryResultThrows() { - Connection con = Jsoup.connect(FileServlet.Url); - con.data(FileServlet.LocationParam, "/htmltests/thumb.jpg"); + Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")); con.data(FileServlet.ContentTypeParam, "text/html"); boolean threw = false; @@ -460,8 +458,7 @@ public void testBinaryResultThrows() { @Test public void testBinaryContentTypeThrowsException() { - Connection con = Jsoup.connect(FileServlet.Url); - con.data(FileServlet.LocationParam, "/htmltests/thumb.jpg"); + Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")); con.data(FileServlet.ContentTypeParam, "image/jpeg"); boolean threw = false; @@ -477,8 +474,7 @@ public void testBinaryContentTypeThrowsException() { @Test public void canFetchBinaryAsBytes() throws IOException { - Connection.Response res = Jsoup.connect(FileServlet.Url) - .data(FileServlet.LocationParam, "/htmltests/thumb.jpg") + Connection.Response res = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")) .data(FileServlet.ContentTypeParam, "image/jpeg") .ignoreContentType(true) .execute(); diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java index bf1a089ef0..a4fb2b9660 100644 --- a/src/test/java/org/jsoup/integration/TestServer.java +++ b/src/test/java/org/jsoup/integration/TestServer.java @@ -51,7 +51,7 @@ public static String map(Class servletClass) { start(); // if running out of the test cases String path = "/" + servletClass.getSimpleName(); - handler.addServletWithMapping(servletClass, path); + handler.addServletWithMapping(servletClass, path + "/*"); int port = ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); return "http://localhost:" + port + path; } diff --git a/src/test/java/org/jsoup/integration/servlets/FileServlet.java b/src/test/java/org/jsoup/integration/servlets/FileServlet.java index 5a727cc06c..4a595796f0 100644 --- a/src/test/java/org/jsoup/integration/servlets/FileServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/FileServlet.java @@ -10,7 +10,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; + public class FileServlet extends BaseServlet { public static final String Url = TestServer.map(FileServlet.class); @@ -23,7 +23,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE String contentType = req.getParameter(ContentTypeParam); if (contentType == null) contentType = DefaultType; - String location = req.getParameter(LocationParam); + String location = req.getPathInfo(); File file = ParseTest.getFile(location); if (file.exists()) { @@ -38,6 +38,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE } } + public static String urlTo(String path) { + return Url + path; + } + @Override protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doGet(req, res); From 0fc3d6728ae270fb38f9778ad7fa2663060b50c7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 4 Jul 2019 15:26:11 -0700 Subject: [PATCH 331/774] On duplicate attributes, retain first not last instance Fixes #1219 --- CHANGES | 4 ++ src/main/java/org/jsoup/nodes/Attributes.java | 42 +++++++++++++++++-- .../org/jsoup/parser/HtmlTreeBuilder.java | 12 +++++- .../java/org/jsoup/parser/ParseSettings.java | 14 +++++++ src/main/java/org/jsoup/parser/Token.java | 4 +- .../java/org/jsoup/parser/TreeBuilder.java | 11 +++++ .../java/org/jsoup/parser/XmlTreeBuilder.java | 2 + .../java/org/jsoup/parser/HtmlParserTest.java | 19 +++++++++ .../org/jsoup/parser/XmlTreeBuilderTest.java | 10 +++++ 9 files changed, 111 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index d50beefb23..6cc6c7578d 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ jsoup changelog * Improvement: added Element chaining methods for various overridden methods on Node. + * Bugfix: if duplicate attributes in an element exist, retain the first vs the last attribute with the same name. Case + aware (HTML case-insensitive names, XML are case-sensitive). + + * Various code hygiene updates. **** Release 1.12.1 [2019-May12] diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index b3fe49a6c7..85dabcf261 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -1,8 +1,9 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; +import org.jsoup.parser.ParseSettings; import java.io.IOException; import java.util.AbstractMap; @@ -111,12 +112,16 @@ public String getIgnoreCase(String key) { return i == NotFound ? EmptyString : checkNotNull(vals[i]); } - // adds without checking if this key exists - private void add(String key, String value) { + /** + * Adds a new attribute. Will produce duplicates if the key already exists. + * @see Attributes#put(String, String) + */ + public Attributes add(String key, String value) { checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; + return this; } /** @@ -230,6 +235,13 @@ public int size() { return size; } + /** + * Test if this Attributes list is empty (size==0). + */ + public boolean isEmpty() { + return size == 0; + } + /** Add all the attributes from the incoming set to this set. @param incoming attributes to add to these attributes. @@ -382,6 +394,30 @@ public void normalize() { } } + /** + * Internal method. Removes duplicate attribute by name. Settings for case sensitivity of key names. + * @param settings case sensitivity + * @return number of removed dupes + */ + public int deduplicate(ParseSettings settings) { + if (isEmpty()) + return 0; + boolean preserve = settings.preserveAttributeCase(); + int dupes = 0; + OUTER: for (int i = 0; i < keys.length; i++) { + for (int j = i + 1; j < keys.length; j++) { + if (keys[j] == null) + continue OUTER; // keys.length doesn't shrink when removing, so re-test + if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) { + dupes++; + remove(j); + j--; + } + } + } + return dupes; + } + private static class Dataset extends AbstractMap { private final Attributes attributes; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index fd7d2bfbbf..099859d18d 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -1,7 +1,7 @@ package org.jsoup.parser; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; import org.jsoup.nodes.DataNode; @@ -194,7 +194,15 @@ void error(HtmlTreeBuilderState state) { parser.getErrors().add(new ParseError(reader.pos(), "Unexpected token [%s] when in state [%s]", currentToken.tokenType(), state)); } - Element insert(Token.StartTag startTag) { + Element insert(final Token.StartTag startTag) { + // cleanup duplicate attributes: + if (!startTag.attributes.isEmpty()) { + int dupes = startTag.attributes.deduplicate(settings); + if (dupes > 0) { + error("Duplicate attribute"); + } + } + // handle empty unknown tags // when the spec expects an empty tag, will directly hit insertEmpty, so won't generate this fake end tag. if (startTag.isSelfClosing()) { diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index b12c3f2943..18c685a489 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -25,6 +25,20 @@ public class ParseSettings { private final boolean preserveTagCase; private final boolean preserveAttributeCase; + /** + * Returns true if preserving tag name case. + */ + public boolean preserveTagCase() { + return preserveTagCase; + } + + /** + * Returns true if preserving attribute case. + */ + public boolean preserveAttributeCase() { + return preserveAttributeCase; + } + /** * Define parse settings. * @param tag preserve tag case? diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 55866ed18d..ca26404c08 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -112,7 +112,8 @@ else if (hasEmptyAttributeValue) value = ""; else value = null; - attributes.put(pendingAttributeName, value); + // note that we add, not put. So that the first is kept, and rest are deduped, once in a context where case sensitivity is known (the appropriate tree builder). + attributes.add(pendingAttributeName, value); } } pendingAttributeName = null; @@ -125,7 +126,6 @@ else if (hasEmptyAttributeValue) final void finaliseTag() { // finalises for emit if (pendingAttributeName != null) { - // todo: check if attribute name exists; if so, drop and error newAttribute(); } } diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 75dd91a786..b54f06aa73 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -91,4 +91,15 @@ protected Element currentElement() { int size = stack.size(); return size > 0 ? stack.get(size-1) : null; } + + + /** + * If the parser is tracking errors, and an error at the current position. + * @param msg error message + */ + protected void error(String msg) { + ParseErrorList errors = parser.getErrors(); + if (errors.canAddError()) + errors.add(new ParseError(reader.pos(), msg)); + } } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 87cdc3728d..7fbe251920 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -75,6 +75,8 @@ private void insertNode(Node node) { Element insert(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name(), settings); // todo: wonder if for xml parsing, should treat all tags as unknown? because it's not html. + startTag.attributes.deduplicate(settings); + Element el = new Element(tag, baseUri, settings.normalizeAttributes(startTag.attributes)); insertNode(el); if (startTag.isSelfClosing()) { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 6c83c1e097..ad3876566e 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -55,6 +55,25 @@ public class HtmlParserTest { assertEquals("foo > bar", p.attr("class")); } + @Test public void dropsDuplicateAttributes() { + String html = "

    Text

    "; + Parser parser = Parser.htmlParser().setTrackErrors(10); + Document doc = parser.parseInput(html, ""); + + Element p = doc.selectFirst("p"); + assertEquals("

    Text

    ", p.outerHtml()); // normalized names due to lower casing + + assertEquals(1, parser.getErrors().size()); + assertEquals("Duplicate attribute", parser.getErrors().get(0).getErrorMessage()); + } + + @Test public void retainsAttributesOfDifferentCaseIfSensitive() { + String html = "

    Text

    "; + Parser parser = Parser.htmlParser().settings(ParseSettings.preserveCase); + Document doc = parser.parseInput(html, ""); + assertEquals("

    Text

    ", doc.selectFirst("p").outerHtml()); + } + @Test public void parsesQuiteRoughAttributes() { String html = "

    OneSomething

    Else"; // this (used to; now gets cleaner) gets a

    with attr '=a' and an var a=\"\n \"; ", doc.html()); // converted from pseudo xmldecl to comment } + + @Test public void dropsDuplicateAttributes() { + // case sensitive, so should drop Four and Five + String html = "

    Text

    "; + Parser parser = Parser.xmlParser().setTrackErrors(10); + Document doc = parser.parseInput(html, ""); + + assertEquals("

    Text

    ", doc.selectFirst("p").outerHtml()); + } + } From e9e613706443fd1cd3458d1e5116c0498afd8b09 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 4 Jul 2019 15:31:18 -0700 Subject: [PATCH 332/774] Testcase for #1234 Fixes #1234 --- src/test/java/org/jsoup/nodes/FormElementTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index 3b20f0701c..982b1835fd 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -46,6 +46,13 @@ public class FormElementTest { // ten should not appear, disabled } + @Test public void formDataUsesFirstAttribute() { + String html = "
    "; + Document doc = Jsoup.parse(html); + FormElement form = (FormElement) doc.selectFirst("form"); + assertEquals("test=foo", form.formData().get(0).toString()); + } + @Test public void createsSubmitableConnection() { String html = ""; Document doc = Jsoup.parse(html, "http://example.com/"); From a9439f0c503ac8e0f009a0ec39f8bff1a6271d51 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 4 Jul 2019 15:54:14 -0700 Subject: [PATCH 333/774] Don't submit button values Fixes #1231 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/FormElement.java | 4 +++- src/test/java/org/jsoup/nodes/FormElementTest.java | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6cc6c7578d..6a6b7f08f0 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog aware (HTML case-insensitive names, XML are case-sensitive). + * Bugfix: don't submit input type=button form elements. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May12] diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index f5c59d331c..57702894dd 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -86,7 +86,9 @@ public List formData() { if (name.length() == 0) continue; String type = el.attr("type"); - if ("select".equals(el.tagName())) { + if (type.equalsIgnoreCase("button")) continue; // browsers don't submit these + + if ("select".equals(el.normalName())) { Elements options = el.select("option[selected]"); boolean set = false; for (Element option: options) { diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index 982b1835fd..a2221e8722 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -30,6 +30,7 @@ public class FormElementTest { "" + "" + "" + + "" + ""; Document doc = Jsoup.parse(html); FormElement form = (FormElement) doc.select("form").first(); @@ -44,6 +45,7 @@ public class FormElementTest { assertEquals("eight=on", data.get(5).toString()); // default // nine should not appear, not checked checkbox // ten should not appear, disabled + // eleven should not appear, button } @Test public void formDataUsesFirstAttribute() { From b61a3e6b340b878b30c518e35c6066f559b5102e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 4 Jul 2019 17:49:17 -0700 Subject: [PATCH 334/774] Fix Travis CI JDK issues (#1236) Fixes #1235 --- .travis.yml | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 11a4a2602e..93cfb53e67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,38 @@ -language: java +# From https://github.com/DanySK/Gravis-CI + +language: bash dist: xenial +os: + - linux + +env: + global: + - GRAVIS="https://raw.githubusercontent.com/DanySK/Gravis-CI/master/" -jdk: - - openjdk9 - - openjdk10 - - openjdk11 - - openjdk12 + matrix: + # List any JDK you want to build your software with. + # You can see the list of supported environments by installing Jabba and using ls-remote: + # https://github.com/shyiko/jabba#usage + - JDK="openjdk-ri@1.7.75" + - JDK="adopt@1.8.212-04" + - JDK="amazon-corretto@1.8.212-04.2" + - JDK="openjdk@1.9.0-4" + - JDK="1.12.0-1" cache: - directories: - - $HOME/.m2 + directories: + # Avoid re-downloading the JDK every time + - $HOME/.jabba/ + - $HOME/.m2/ + +before_install: + # Sets up the Java environment + - curl "${GRAVIS}.install-jdk-travis.sh" --output .install-jdk-travis.sh + # Get maven 3.1.1 + - "wget http://apache.mirror.iphh.net/maven/maven-3/3.1.1/binaries/apache-maven-3.1.1-bin.tar.gz && tar xfz apache-maven-3.1.1-bin.tar.gz && sudo mv apache-maven-3.1.1 /usr/local/maven-3.1.1 && sudo rm -f /usr/local/maven && sudo ln -s /usr/local/maven-3.1.1 /usr/local/maven" + +before_script: + - bash .install-jdk-travis.sh script: - - mvn verify -B + - /usr/local/maven/bin/mvn verify -B From fb588770a45a9ab6b64fce0f800d50ec225a42ee Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 4 Jul 2019 20:38:30 -0700 Subject: [PATCH 335/774] Reorder conn.disconnect, to support keepalives when bodyStream read Fixes #1232 --- CHANGES | 3 +++ src/main/java/org/jsoup/helper/HttpConnection.java | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 6a6b7f08f0..94d3e5242c 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ jsoup changelog * Improvement: added Element chaining methods for various overridden methods on Node. + * Improvement: ensure HTTP keepalives work when fetching content via body() and bodyAsBytes(). + + * Bugfix: if duplicate attributes in an element exist, retain the first vs the last attribute with the same name. Case aware (HTML case-insensitive names, XML are case-sensitive). diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 4063130d72..aad10ab770 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -915,13 +915,10 @@ private static HttpURLConnection createConnection(Connection.Request req) throws } /** - * Call on completion of stream read, to close the body (or error) stream + * Call on completion of stream read, to close the body (or error) stream. The connection.disconnect allows + * keep-alives to work (as the underlying connection is actually held open, despite the name). */ private void safeClose() { - if (conn != null) { - conn.disconnect(); - conn = null; - } if (bodyStream != null) { try { bodyStream.close(); @@ -931,6 +928,10 @@ private void safeClose() { bodyStream = null; } } + if (conn != null) { + conn.disconnect(); + conn = null; + } } // set up url, method, header, cookies From 8d1d503913a68e549b5c4a94717c62cf3f64507a Mon Sep 17 00:00:00 2001 From: jhy Date: Thu, 4 Jul 2019 21:19:46 -0700 Subject: [PATCH 336/774] Removed external URL from testsuite Don't want external dependencies on the build --- src/test/java/org/jsoup/integration/ConnectTest.java | 6 +++++- .../org/jsoup/integration/servlets/RedirectServlet.java | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index edc5c3f115..95f6e325f9 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -313,7 +313,11 @@ public void postFiles() throws IOException { @Test public void multiCookieSet() throws IOException { - Connection con = Jsoup.connect("http://direct.infohound.net/tools/302-cookie.pl"); + Connection con = Jsoup + .connect(RedirectServlet.Url) + .data(RedirectServlet.CodeParam, "302") + .data(RedirectServlet.SetCookiesParam, "true") + .data(RedirectServlet.LocationParam, echoUrl); Connection.Response res = con.execute(); // test cookies set by redirect: diff --git a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java index 8431347750..a6784c4853 100644 --- a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java @@ -3,6 +3,7 @@ import org.jsoup.integration.TestServer; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -11,6 +12,7 @@ public class RedirectServlet extends BaseServlet { public static final String Url = TestServer.map(RedirectServlet.class); public static final String LocationParam = "loc"; public static final String CodeParam = "code"; + public static final String SetCookiesParam = "setCookies"; private static final int DefaultCode = HttpServletResponse.SC_MOVED_TEMPORARILY; @Override @@ -24,6 +26,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE if (code != null) intCode = Integer.parseInt(code); + if (req.getParameter(SetCookiesParam) != null) { + res.addCookie(new Cookie("token", "asdfg123")); + res.addCookie(new Cookie("uid", "jhy")); + } + res.setHeader("Location", location); res.setStatus(intCode); } From 468c5369b52ca45de3c7e54a3d2ddae352495851 Mon Sep 17 00:00:00 2001 From: Leos Literak Date: Wed, 1 Jan 2020 18:06:16 +0100 Subject: [PATCH 337/774] Added new method childrenCount complementary to child(int) --- src/main/java/org/jsoup/nodes/Element.java | 12 ++++++++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 4a7403281f..ba8d1fc753 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -264,6 +264,18 @@ public Element child(int index) { return childElementsList().get(index); } + /** + * Get the number of child nodes that are elements. + *

    + * This method works on the same filtered list like {@link #child(int)}. + *

    + * @return the number of child nodes that are elements + * @see #child(int) + */ + public int childrenCount() { + return childElementsList().size(); + } + /** * Get this element's child elements. *

    diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index a41653577b..ef93139982 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1482,4 +1482,12 @@ public FilterResult tail(Node node, int depth) { assertSame(div, div2); } + + @Test + public void testChildMixedContent() { + Document doc = Jsoup.parse("

    PatternMatchesExample
    *any element*
    tagelements with the given tag namediv
    \n\n\n\n\n
    15:00sport
    "); + Element row = doc.selectFirst("table tbody tr"); + assertSame(2, row.childrenCount()); + assertFalse(row.childrenCount() == row.childNodeSize()); + } } From a0b87bf10a9a520b49748c619c868caed8d7a109 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 20 Jan 2020 18:10:05 -0800 Subject: [PATCH 338/774] Ensure enough data is buffered to survive a Mark reset Fixes #1218 --- CHANGES | 7 ++++- src/main/java/org/jsoup/helper/Validate.java | 8 ++++++ .../org/jsoup/parser/CharacterReader.java | 28 ++++++++++++++++--- src/main/java/org/jsoup/parser/Tokeniser.java | 16 +++++++++-- .../java/org/jsoup/parser/TokeniserState.java | 5 ++++ .../org/jsoup/integration/ConnectTest.java | 17 +++++++++++ .../org/jsoup/parser/CharacterReaderTest.java | 25 ++++++++++++++++- .../htmltests/escapes-across-buffer.html | 5 ++++ 8 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 src/test/resources/htmltests/escapes-across-buffer.html diff --git a/CHANGES b/CHANGES index 94d3e5242c..b2eb976d6e 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,11 @@ jsoup changelog * Improvement: ensure HTTP keepalives work when fetching content via body() and bodyAsBytes(). + * Bugfix: on pages fetch by Jsoup.Connection, a "Mark Invalid" exception might be incorrectly thrown, or the page may + miss some data. This occurred on larger pages when the file transfer was chunked, an an invalid HTML entity happened + to cross a chunk boundary. + + * Bugfix: if duplicate attributes in an element exist, retain the first vs the last attribute with the same name. Case aware (HTML case-insensitive names, XML are case-sensitive). @@ -20,7 +25,7 @@ jsoup changelog * Various code hygiene updates. -**** Release 1.12.1 [2019-May12] +**** Release 1.12.1 [2019-May-12] * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates(). * Change: some internal methods have been rearranged; if you extended any of the Jsoup internals you may need to make diff --git a/src/main/java/org/jsoup/helper/Validate.java b/src/main/java/org/jsoup/helper/Validate.java index 814bcc3a40..01eb80a654 100644 --- a/src/main/java/org/jsoup/helper/Validate.java +++ b/src/main/java/org/jsoup/helper/Validate.java @@ -102,6 +102,14 @@ public static void notEmpty(String string, String msg) { throw new IllegalArgumentException(msg); } + /** + * Blow up if we reach an unexpected state. + * @param msg message to think about + */ + public static void wtf(String msg) { + throw new IllegalStateException(msg); + } + /** Cause a failure. @param msg message to output. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 01fd4220a6..515dcc729f 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -17,6 +17,7 @@ public final class CharacterReader { private static final int maxStringCacheLen = 12; static final int maxBufferLen = 1024 * 32; // visible for testing private static final int readAheadLimit = (int) (maxBufferLen * 0.75); + private static final int minReadAheadLen = 1024; // the minimum mark length supported. No HTML entities can be larger than this. private final char[] charBuf; private final Reader reader; @@ -47,7 +48,11 @@ public CharacterReader(String input) { this(new StringReader(input), input.length()); } + private boolean readFully; // if the underlying stream has been completely read, no value in further buffering private void bufferUp() { + if (readFully) + return; + final int pos = bufPos; if (pos < bufSplitPoint) return; @@ -55,9 +60,17 @@ private void bufferUp() { try { final long skipped = reader.skip(pos); reader.mark(maxBufferLen); - final int read = reader.read(charBuf); + int read = 0; + while (read <= minReadAheadLen) { + int thisRead = reader.read(charBuf, read, charBuf.length - read); + if (thisRead == -1) + readFully = true; + if (thisRead <= 0) + break; + read += thisRead; + } reader.reset(); - if (read != -1) { + if (read > 0) { Validate.isTrue(skipped == pos); // Previously asserted that there is room in buf to skip, so this will be a WTF bufLength = read; readerPos += pos; @@ -122,17 +135,24 @@ public void advance() { } void mark() { - // extra buffer up, to get as much rewind capacity as possible - bufSplitPoint = 0; + // make sure there is enough look ahead capacity + if (bufLength - bufPos < minReadAheadLen) + bufSplitPoint = 0; + bufferUp(); bufMark = bufPos; } + void unmark() { + bufMark = -1; + } + void rewindToMark() { if (bufMark == -1) throw new UncheckedIOException(new IOException("Mark invalid")); bufPos = bufMark; + unmark(); } /** diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 22e1ce1d5e..b20dd6b191 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -1,7 +1,7 @@ package org.jsoup.parser; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Entities; import java.util.Arrays; @@ -53,8 +53,16 @@ final class Tokeniser { } Token read() { - while (!isEmitPending) + int pos = reader.pos(); // count how many reads we do in a row without making progress, and bail if stuck in a loop + int consecutiveReads = 0; + while (!isEmitPending) { state.read(this, reader); + if (reader.pos() <= pos) { + consecutiveReads++; + } + Validate.isTrue(consecutiveReads < 10, + "BUG: Not making progress from state: " + this.state.name() + " with current char=" + reader.current()); + } // if emit is pending, a non-character token was found: return any chars in buffer, and leave token for next read: if (charsBuilder.length() > 0) { @@ -147,6 +155,8 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in reader.rewindToMark(); return null; } + + reader.unmark(); if (!reader.matchConsume(";")) characterReferenceError("missing semicolon"); // missing semi int charval = -1; @@ -189,6 +199,8 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in reader.rewindToMark(); return null; } + + reader.unmark(); if (!reader.matchConsume(";")) characterReferenceError("missing semicolon"); // missing semi int numChars = Entities.codepointsForName(nameRef, multipointHolder); diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 95b326cc77..bb21d0f6e5 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -288,6 +288,11 @@ void read(Tokeniser t, CharacterReader r) { t.emit(" + +&aaaa;&aaab;&aaac;&aaad;&aaae;&aaaf;&aaag;&aaah;&aaai;&aaba;&aabb;&aabc;&aabd;&aabe;&aabf;&aabg;&aabh;&aabi;&aaca;&aacb;&aacc;&aacd;&aace;&aacf;&aacg;&aach;&aaci;&aada;&aadb;&aadc;&aadd;&aade;&aadf;&aadg;&aadh;&aadi;&aaea;&aaeb;&aaec;&aaed;&aaee;&aaef;&aaeg;&aaeh;&aaei;&aafa;&aafb;&aafc;&aafd;&aafe;&aaff;&aafg;&aafh;&aafi;&aaga;&aagb;&aagc;&aagd;&aage;&aagf;&aagg;&aagh;&aagi;&aaha;&aahb;&aahc;&aahd;&aahe;&aahf;&aahg;&aahh;&aahi;&aaia;&aaib;&aaic;&aaid;&aaie;&aaif;&aaig;&aaih;&aaii;&abaa;&abab;&abac;&abad;&abae;&abaf;&abag;&abah;&abai;&abba;&abbb;&abbc;&abbd;&abbe;&abbf;&abbg;&abbh;&abbi;&abca;&abcb;&abcc;&abcd;&abce;&abcf;&abcg;&abch;&abci;&abda;&abdb;&abdc;&abdd;&abde;&abdf;&abdg;&abdh;&abdi;&abea;&abeb;&abec;&abed;&abee;&abef;&abeg;&abeh;&abei;&abfa;&abfb;&abfc;&abfd;&abfe;&abff;&abfg;&abfh;&abfi;&abga;&abgb;&abgc;&abgd;&abge;&abgf;&abgg;&abgh;&abgi;&abha;&abhb;&abhc;&abhd;&abhe;&abhf;&abhg;&abhh;&abhi;&abia;&abib;&abic;&abid;&abie;&abif;&abig;&abih;&abii;&acaa;&acab;&acac;&acad;&acae;&acaf;&acag;&acah;&acai;&acba;&acbb;&acbc;&acbd;&acbe;&acbf;&acbg;&acbh;&acbi;&acca;&accb;&accc;&accd;&acce;&accf;&accg;&acch;&acci;&acda;&acdb;&acdc;&acdd;&acde;&acdf;&acdg;&acdh;&acdi;&acea;&aceb;&acec;&aced;&acee;&acef;&aceg;&aceh;&acei;&acfa;&acfb;&acfc;&acfd;&acfe;&acff;&acfg;&acfh;&acfi;&acga;&acgb;&acgc;&acgd;&acge;&acgf;&acgg;&acgh;&acgi;&acha;&achb;&achc;&achd;&ache;&achf;&achg;&achh;&achi;&acia;&acib;&acic;&acid;&acie;&acif;&acig;&acih;&acii;&adaa;&adab;&adac;&adad;&adae;&adaf;&adag;&adah;&adai;&adba;&adbb;&adbc;&adbd;&adbe;&adbf;&adbg;&adbh;&adbi;&adca;&adcb;&adcc;&adcd;&adce;&adcf;&adcg;&adch;&adci;&adda;&addb;&addc;&addd;&adde;&addf;&addg;&addh;&addi;&adea;&adeb;&adec;&aded;&adee;&adef;&adeg;&adeh;&adei;&adfa;&adfb;&adfc;&adfd;&adfe;&adff;&adfg;&adfh;&adfi;&adga;&adgb;&adgc;&adgd;&adge;&adgf;&adgg;&adgh;&adgi;&adha;&adhb;&adhc;&adhd;&adhe;&adhf;&adhg;&adhh;&adhi;&adia;&adib;&adic;&adid;&adie;&adif;&adig;&adih;&adii;&aeaa;&aeab;&aeac;&aead;&aeae;&aeaf;&aeag;&aeah;&aeai;&aeba;&aebb;&aebc;&aebd;&aebe;&aebf;&aebg;&aebh;&aebi;&aeca;&aecb;&aecc;&aecd;&aece;&aecf;&aecg;&aech;&aeci;&aeda;&aedb;&aedc;&aedd;&aede;&aedf;&aedg;&aedh;&aedi;&aeea;&aeeb;&aeec;&aeed;&aeee;&aeef;&aeeg;&aeeh;&aeei;&aefa;&aefb;&aefc;&aefd;&aefe;&aeff;&aefg;&aefh;&aefi;&aega;&aegb;&aegc;&aegd;&aege;&aegf;&aegg;&aegh;&aegi;&aeha;&aehb;&aehc;&aehd;&aehe;&aehf;&aehg;&aehh;&aehi;&aeia;&aeib;&aeic;&aeid;&aeie;&aeif;&aeig;&aeih;&aeii;&afaa;&afab;&afac;&afad;&afae;&afaf;&afag;&afah;&afai;&afba;&afbb;&afbc;&afbd;&afbe;&afbf;&afbg;&afbh;&afbi;&afca;&afcb;&afcc;&afcd;&afce;&afcf;&afcg;&afch;&afci;&afda;&afdb;&afdc;&afdd;&afde;&afdf;&afdg;&afdh;&afdi;&afea;&afeb;&afec;&afed;&afee;&afef;&afeg;&afeh;&afei;&affa;&affb;&affc;&affd;&affe;&afff;&affg;&affh;&affi;&afga;&afgb;&afgc;&afgd;&afge;&afgf;&afgg;&afgh;&afgi;&afha;&afhb;&afhc;&afhd;&afhe;&afhf;&afhg;&afhh;&afhi;&afia;&afib;&afic;&afid;&afie;&afif;&afig;&afih;&afii;&agaa;&agab;&agac;&agad;&agae;&agaf;&agag;&agah;&agai;&agba;&agbb;&agbc;&agbd;&agbe;&agbf;&agbg;&agbh;&agbi;&agca;&agcb;&agcc;&agcd;&agce;&agcf;&agcg;&agch;&agci;&agda;&agdb;&agdc;&agdd;&agde;&agdf;&agdg;&agdh;&agdi;&agea;&ageb;&agec;&aged;&agee;&agef;&ageg;&ageh;&agei;&agfa;&agfb;&agfc;&agfd;&agfe;&agff;&agfg;&agfh;&agfi;&agga;&aggb;&aggc;&aggd;&agge;&aggf;&aggg;&aggh;&aggi;&agha;&aghb;&aghc;&aghd;&aghe;&aghf;&aghg;&aghh;&aghi;&agia;&agib;&agic;&agid;&agie;&agif;&agig;&agih;&agii;&ahaa;&ahab;&ahac;&ahad;&ahae;&ahaf;&ahag;&ahah;&ahai;&ahba;&ahbb;&ahbc;&ahbd;&ahbe;&ahbf;&ahbg;&ahbh;&ahbi;&ahca;&ahcb;&ahcc;&ahcd;&ahce;&ahcf;&ahcg;&ahch;&ahci;&ahda;&ahdb;&ahdc;&ahdd;&ahde;&ahdf;&ahdg;&ahdh;&ahdi;&ahea;&aheb;&ahec;&ahed;&ahee;&ahef;&aheg;&aheh;&ahei;&ahfa;&ahfb;&ahfc;&ahfd;&ahfe;&ahff;&ahfg;&ahfh;&ahfi;&ahga;&ahgb;&ahgc;&ahgd;&ahge;&ahgf;&ahgg;&ahgh;&ahgi;&ahha;&ahhb;&ahhc;&ahhd;&ahhe;&ahhf;&ahhg;&ahhh;&ahhi;&ahia;&ahib;&ahic;&ahid;&ahie;&ahif;&ahig;&ahih;&ahii;&aiaa;&aiab;&aiac;&aiad;&aiae;&aiaf;&aiag;&aiah;&aiai;&aiba;&aibb;&aibc;&aibd;&aibe;&aibf;&aibg;&aibh;&aibi;&aica;&aicb;&aicc;&aicd;&aice;&aicf;&aicg;&aich;&aici;&aida;&aidb;&aidc;&aidd;&aide;&aidf;&aidg;&aidh;&aidi;&aiea;&aieb;&aiec;&aied;&aiee;&aief;&aieg;&aieh;&aiei;&aifa;&aifb;&aifc;&aifd;&aife;&aiff;&aifg;&aifh;&aifi;&aiga;&aigb;&aigc;&aigd;&aige;&aigf;&aigg;&aigh;&aigi;&aiha;&aihb;&aihc;&aihd;&aihe;&aihf;&aihg;&aihh;&aihi;&aiia;&aiib;&aiic;&aiid;&aiie;&aiif;&aiig;&aiih;&aiii;&baaa;&baab;&baac;&baad;&baae;&baaf;&baag;&baah;&baai;&baba;&babb;&babc;&babd;&babe;&babf;&babg;&babh;&babi;&baca;&bacb;&bacc;&bacd;&bace;&bacf;&bacg;&bach;&baci;&bada;&badb;&badc;&badd;&bade;&badf;&badg;&badh;&badi;&baea;&baeb;&baec;&baed;&baee;&baef;&baeg;&baeh;&baei;&bafa;&bafb;&bafc;&bafd;&bafe;&baff;&bafg;&bafh;&bafi;&baga;&bagb;&bagc;&bagd;&bage;&bagf;&bagg;&bagh;&bagi;&baha;&bahb;&bahc;&bahd;&bahe;&bahf;&bahg;&bahh;&bahi;&baia;&baib;&baic;&baid;&baie;&baif;&baig;&baih;&baii;&bbaa;&bbab;&bbac;&bbad;&bbae;&bbaf;&bbag;&bbah;&bbai;&bbba;&bbbb;&bbbc;&bbbd;&bbbe;&bbbf;&bbbg;&bbbh;&bbbi;&bbca;&bbcb;&bbcc;&bbcd;&bbce;&bbcf;&bbcg;&bbch;&bbci;&bbda;&bbdb;&bbdc;&bbdd;&bbde;&bbdf;&bbdg;&bbdh;&bbdi;&bbea;&bbeb;&bbec;&bbed;&bbee;&bbef;&bbeg;&bbeh;&bbei;&bbfa;&bbfb;&bbfc;&bbfd;&bbfe;&bbff;&bbfg;&bbfh;&bbfi;&bbga;&bbgb;&bbgc;&bbgd;&bbge;&bbgf;&bbgg;&bbgh;&bbgi;&bbha;&bbhb;&bbhc;&bbhd;&bbhe;&bbhf;&bbhg;&bbhh;&bbhi;&bbia;&bbib;&bbic;&bbid;&bbie;&bbif;&bbig;&bbih;&bbii;&bcaa;&bcab;&bcac;&bcad;&bcae;&bcaf;&bcag;&bcah;&bcai;&bcba;&bcbb;&bcbc;&bcbd;&bcbe;&bcbf;&bcbg;&bcbh;&bcbi;&bcca;&bccb;&bccc;&bccd;&bcce;&bccf;&bccg;&bcch;&bcci;&bcda;&bcdb;&bcdc;&bcdd;&bcde;&bcdf;&bcdg;&bcdh;&bcdi;&bcea;&bceb;&bcec;&bced;&bcee;&bcef;&bceg;&bceh;&bcei;&bcfa;&bcfb;&bcfc;&bcfd;&bcfe;&bcff;&bcfg;&bcfh;&bcfi;&bcga;&bcgb;&bcgc;&bcgd;&bcge;&bcgf;&bcgg;&bcgh;&bcgi;&bcha;&bchb;&bchc;&bchd;&bche;&bchf;&bchg;&bchh;&bchi;&bcia;&bcib;&bcic;&bcid;&bcie;&bcif;&bcig;&bcih;&bcii;&bdaa;&bdab;&bdac;&bdad;&bdae;&bdaf;&bdag;&bdah;&bdai;&bdba;&bdbb;&bdbc;&bdbd;&bdbe;&bdbf;&bdbg;&bdbh;&bdbi;&bdca;&bdcb;&bdcc;&bdcd;&bdce;&bdcf;&bdcg;&bdch;&bdci;&bdda;&bddb;&bddc;&bddd;&bdde;&bddf;&bddg;&bddh;&bddi;&bdea;&bdeb;&bdec;&bded;&bdee;&bdef;&bdeg;&bdeh;&bdei;&bdfa;&bdfb;&bdfc;&bdfd;&bdfe;&bdff;&bdfg;&bdfh;&bdfi;&bdga;&bdgb;&bdgc;&bdgd;&bdge;&bdgf;&bdgg;&bdgh;&bdgi;&bdha;&bdhb;&bdhc;&bdhd;&bdhe;&bdhf;&bdhg;&bdhh;&bdhi;&bdia;&bdib;&bdic;&bdid;&bdie;&bdif;&bdig;&bdih;&bdii;&beaa;&beab;&beac;&bead;&beae;&beaf;&beag;&beah;&beai;&beba;&bebb;&bebc;&bebd;&bebe;&bebf;&bebg;&bebh;&bebi;&beca;&becb;&becc;&becd;&bece;&becf;&becg;&bech;&beci;&beda;&bedb;&bedc;&bedd;&bede;&bedf;&bedg;&bedh;&bedi;&beea;&beeb;&beec;&beed;&beee;&beef;&beeg;&beeh;&beei;&befa;&befb;&befc;&befd;&befe;&beff;&befg;&befh;&befi;&bega;&begb;&begc;&begd;&bege;&begf;&begg;&begh;&begi;&beha;&behb;&behc;&behd;&behe;&behf;&behg;&behh;&behi;&beia;&beib;&beic;&beid;&beie;&beif;&beig;&beih;&beii;&bfaa;&bfab;&bfac;&bfad;&bfae;&bfaf;&bfag;&bfah;&bfai;&bfba;&bfbb;&bfbc;&bfbd;&bfbe;&bfbf;&bfbg;&bfbh;&bfbi;&bfca;&bfcb;&bfcc;&bfcd;&bfce;&bfcf;&bfcg;&bfch;&bfci;&bfda;&bfdb;&bfdc;&bfdd;&bfde;&bfdf;&bfdg;&bfdh;&bfdi;&bfea;&bfeb;&bfec;&bfed;&bfee;&bfef;&bfeg;&bfeh;&bfei;&bffa;&bffb;&bffc;&bffd;&bffe;&bfff;&bffg;&bffh;&bffi;&bfga;&bfgb;&bfgc;&bfgd;&bfge;&bfgf;&bfgg;&bfgh;&bfgi;&bfha;&bfhb;&bfhc;&bfhd;&bfhe;&bfhf;&bfhg;&bfhh;&bfhi;&bfia;&bfib;&bfic;&bfid;&bfie;&bfif;&bfig;&bfih;&bfii;&bgaa;&bgab;&bgac;&bgad;&bgae;&bgaf;&bgag;&bgah;&bgai;&bgba;&bgbb;&bgbc;&bgbd;&bgbe;&bgbf;&bgbg;&bgbh;&bgbi;&bgca;&bgcb;&bgcc;&bgcd;&bgce;&bgcf;&bgcg;&bgch;&bgci;&bgda;&bgdb;&bgdc;&bgdd;&bgde;&bgdf;&bgdg;&bgdh;&bgdi;&bgea;&bgeb;&bgec;&bged;&bgee;&bgef;&bgeg;&bgeh;&bgei;&bgfa;&bgfb;&bgfc;&bgfd;&bgfe;&bgff;&bgfg;&bgfh;&bgfi;&bgga;&bggb;&bggc;&bggd;&bgge;&bggf;&bggg;&bggh;&bggi;&bgha;&bghb;&bghc;&bghd;&bghe;&bghf;&bghg;&bghh;&bghi;&bgia;&bgib;&bgic;&bgid;&bgie;&bgif;&bgig;&bgih;&bgii;&bhaa;&bhab;&bhac;&bhad;&bhae;&bhaf;&bhag;&bhah;&bhai;&bhba;&bhbb;&bhbc;&bhbd;&bhbe;&bhbf;&bhbg;&bhbh;&bhbi;&bhca;&bhcb;&bhcc;&bhcd;&bhce;&bhcf;&bhcg;&bhch;&bhci;&bhda;&bhdb;&bhdc;&bhdd;&bhde;&bhdf;&bhdg;&bhdh;&bhdi;&bhea;&bheb;&bhec;&bhed;&bhee;&bhef;&bheg;&bheh;&bhei;&bhfa;&bhfb;&bhfc;&bhfd;&bhfe;&bhff;&bhfg;&bhfh;&bhfi;&bhga;&bhgb;&bhgc;&bhgd;&bhge;&bhgf;&bhgg;&bhgh;&bhgi;&bhha;&bhhb;&bhhc;&bhhd;&bhhe;&bhhf;&bhhg;&bhhh;&bhhi;&bhia;&bhib;&bhic;&bhid;&bhie;&bhif;&bhig;&bhih;&bhii;&biaa;&biab;&biac;&biad;&biae;&biaf;&biag;&biah;&biai;&biba;&bibb;&bibc;&bibd;&bibe;&bibf;&bibg;&bibh;&bibi;&bica;&bicb;&bicc;&bicd;&bice;&bicf;&bicg;&bich;&bici;&bida;&bidb;&bidc;&bidd;&bide;&bidf;&bidg;&bidh;&bidi;&biea;&bieb;&biec;&bied;&biee;&bief;&bieg;&bieh;&biei;&bifa;&bifb;&bifc;&bifd;&bife;&biff;&bifg;&bifh;&bifi;&biga;&bigb;&bigc;&bigd;&bige;&bigf;&bigg;&bigh;&bigi;&biha;&bihb;&bihc;&bihd;&bihe;&bihf;&bihg;&bihh;&bihi;&biia;&biib;&biic;&biid;&biie;&biif;&biig;&biih;&biii;&caaa;&caab;&caac;&caad;&caae;&caaf;&caag;&caah;&caai;&caba;&cabb;&cabc;&cabd;&cabe;&cabf;&cabg;&cabh;&cabi;&caca;&cacb;&cacc;&cacd;&cace;&cacf;&cacg;&cach;&caci;&cada;&cadb;&cadc;&cadd;&cade;&cadf;&cadg;&cadh;&cadi;&caea;&caeb;&caec;&caed;&caee;&caef;&caeg;&caeh;&caei;&cafa;&cafb;&cafc;&cafd;&cafe;&caff;&cafg;&cafh;&cafi;&caga;&cagb;&cagc;&cagd;&cage;&cagf;&cagg;&cagh;&cagi;&caha;&cahb;&cahc;&cahd;&cahe;&cahf;&cahg;&cahh;&cahi;&caia;&caib;&caic;&caid;&caie;&caif;&caig;&caih;&caii;&cbaa;&cbab;&cbac;&cbad;&cbae;&cbaf;&cbag;&cbah;&cbai;&cbba;&cbbb;&cbbc;&cbbd;&cbbe;&cbbf;&cbbg;&cbbh;&cbbi;&cbca;&cbcb;&cbcc;&cbcd;&cbce;&cbcf;&cbcg;&cbch;&cbci;&cbda;&cbdb;&cbdc;&cbdd;&cbde;&cbdf;&cbdg;&cbdh;&cbdi;&cbea;&cbeb;&cbec;&cbed;&cbee;&cbef;&cbeg;&cbeh;&cbei;&cbfa;&cbfb;&cbfc;&cbfd;&cbfe;&cbff;&cbfg;&cbfh;&cbfi;&cbga;&cbgb;&cbgc;&cbgd;&cbge;&cbgf;&cbgg;&cbgh;&cbgi;&cbha;&cbhb;&cbhc;&cbhd;&cbhe;&cbhf;&cbhg;&cbhh;&cbhi;&cbia;&cbib;&cbic;&cbid;&cbie;&cbif;&cbig;&cbih;&cbii;&ccaa;&ccab;&ccac;&ccad;&ccae;&ccaf;&ccag;&ccah;&ccai;&ccba;&ccbb;&ccbc;&ccbd;&ccbe;&ccbf;&ccbg;&ccbh;&ccbi;&ccca;&cccb;&cccc;&cccd;&ccce;&cccf;&cccg;&ccch;&ccci;&ccda;&ccdb;&ccdc;&ccdd;&ccde;&ccdf;&ccdg;&ccdh;&ccdi;&ccea;&cceb;&ccec;&cced;&ccee;&ccef;&cceg;&cceh;&ccei;&ccfa;&ccfb;&ccfc;&ccfd;&ccfe;&ccff;&ccfg;&ccfh;&ccfi;&ccga;&ccgb;&ccgc;&ccgd;&ccge;&ccgf;&ccgg;&ccgh;&ccgi;&ccha;&cchb;&cchc;&cchd;&cche;&cchf;&cchg;&cchh;&cchi;&ccia;&ccib;&ccic;&ccid;&ccie;&ccif;&ccig;&ccih;&ccii;&cdaa;&cdab;&cdac;&cdad;&cdae;&cdaf;&cdag;&cdah;&cdai;&cdba;&cdbb;&cdbc;&cdbd;&cdbe;&cdbf;&cdbg;&cdbh;&cdbi;&cdca;&cdcb;&cdcc;&cdcd;&cdce;&cdcf;&cdcg;&cdch;&cdci;&cdda;&cddb;&cddc;&cddd;&cdde;&cddf;&cddg;&cddh;&cddi;&cdea;&cdeb;&cdec;&cded;&cdee;&cdef;&cdeg;&cdeh;&cdei;&cdfa;&cdfb;&cdfc;&cdfd;&cdfe;&cdff;&cdfg;&cdfh;&cdfi;&cdga;&cdgb;&cdgc;&cdgd;&cdge;&cdgf;&cdgg;&cdgh;&cdgi;&cdha;&cdhb;&cdhc;&cdhd;&cdhe;&cdhf;&cdhg;&cdhh;&cdhi;&cdia;&cdib;&cdic;&cdid;&cdie;&cdif;&cdig;&cdih;&cdii;&ceaa;&ceab;&ceac;&cead;&ceae;&ceaf;&ceag;&ceah;&ceai;&ceba;&cebb;&cebc;&cebd;&cebe;&cebf;&cebg;&cebh;&cebi;&ceca;&cecb;&cecc;&cecd;&cece;&cecf;&cecg;&cech;&ceci;&ceda;&cedb;&cedc;&cedd;&cede;&cedf;&cedg;&cedh;&cedi;&ceea;&ceeb;&ceec;&ceed;&ceee;&ceef;&ceeg;&ceeh;&ceei;&cefa;&cefb;&cefc;&cefd;&cefe;&ceff;&cefg;&cefh;&cefi;&cega;&cegb;&cegc;&cegd;&cege;&cegf;&cegg;&cegh;&cegi;&ceha;&cehb;&cehc;&cehd;&cehe;&cehf;&cehg;&cehh;&cehi;&ceia;&ceib;&ceic;&ceid;&ceie;&ceif;&ceig;&ceih;&ceii;&cfaa;&cfab;&cfac;&cfad;&cfae;&cfaf;&cfag;&cfah;&cfai;&cfba;&cfbb;&cfbc;&cfbd;&cfbe;&cfbf;&cfbg;&cfbh;&cfbi;&cfca;&cfcb;&cfcc;&cfcd;&cfce;&cfcf;&cfcg;&cfch;&cfci;&cfda;&cfdb;&cfdc;&cfdd;&cfde;&cfdf;&cfdg;&cfdh;&cfdi;&cfea;&cfeb;&cfec;&cfed;&cfee;&cfef;&cfeg;&cfeh;&cfei;&cffa;&cffb;&cffc;&cffd;&cffe;&cfff;&cffg;&cffh;&cffi;&cfga;&cfgb;&cfgc;&cfgd;&cfge;&cfgf;&cfgg;&cfgh;&cfgi;&cfha;&cfhb;&cfhc;&cfhd;&cfhe;&cfhf;&cfhg;&cfhh;&cfhi;&cfia;&cfib;&cfic;&cfid;&cfie;&cfif;&cfig;&cfih;&cfii;&cgaa;&cgab;&cgac;&cgad;&cgae;&cgaf;&cgag;&cgah;&cgai;&cgba;&cgbb;&cgbc;&cgbd;&cgbe;&cgbf;&cgbg;&cgbh;&cgbi;&cgca;&cgcb;&cgcc;&cgcd;&cgce;&cgcf;&cgcg;&cgch;&cgci;&cgda;&cgdb;&cgdc;&cgdd;&cgde;&cgdf;&cgdg;&cgdh;&cgdi;&cgea;&cgeb;&cgec;&cged;&cgee;&cgef;&cgeg;&cgeh;&cgei;&cgfa;&cgfb;&cgfc;&cgfd;&cgfe;&cgff;&cgfg;&cgfh;&cgfi;&cgga;&cggb;&cggc;&cggd;&cgge;&cggf;&cggg;&cggh;&cggi;&cgha;&cghb;&cghc;&cghd;&cghe;&cghf;&cghg;&cghh;&cghi;&cgia;&cgib;&cgic;&cgid;&cgie;&cgif;&cgig;&cgih;&cgii;&chaa;&chab;&chac;&chad;&chae;&chaf;&chag;&chah;&chai;&chba;&chbb;&chbc;&chbd;&chbe;&chbf;&chbg;&chbh;&chbi;&chca;&chcb;&chcc;&chcd;&chce;&chcf;&chcg;&chch;&chci;&chda;&chdb;&chdc;&chdd;&chde;&chdf;&chdg;&chdh;&chdi;&chea;&cheb;&chec;&ched;&chee;&chef;&cheg;&cheh;&chei;&chfa;&chfb;&chfc;&chfd;&chfe;&chff;&chfg;&chfh;&chfi;&chga;&chgb;&chgc;&chgd;&chge;&chgf;&chgg;&chgh;&chgi;&chha;&chhb;&chhc;&chhd;&chhe;&chhf;&chhg;&chhh;&chhi;&chia;&chib;&chic;&chid;&chie;&chif;&chig;&chih;&chii;&ciaa;&ciab;&ciac;&ciad;&ciae;&ciaf;&ciag;&ciah;&ciai;&ciba;&cibb;&cibc;&cibd;&cibe;&cibf;&cibg;&cibh;&cibi;&cica;&cicb;&cicc;&cicd;&cice;&cicf;&cicg;&cich;&cici;&cida;&cidb;&cidc;&cidd;&cide;&cidf;&cidg;&cidh;&cidi;&ciea;&cieb;&ciec;&cied;&ciee;&cief;&cieg;&cieh;&ciei;&cifa;&cifb;&cifc;&cifd;&cife;&ciff;&cifg;&cifh;&cifi;&ciga;&cigb;&cigc;&cigd;&cige;&cigf;&cigg;&cigh;&cigi;&ciha;&cihb;&cihc;&cihd;&cihe;&cihf;&cihg;&cihh;&cihi;&ciia;&ciib;&ciic;&ciid;&ciie;&ciif;&ciig;&ciih;&ciii;&daaa;&daab;&daac;&daad;&daae;&daaf;&daag;&daah;&daai;&daba;&dabb;&dabc;&dabd;&dabe;&dabf;&dabg;&dabh;&dabi;&daca;&dacb;&dacc;&dacd;&dace;&dacf;&dacg;&dach;&daci;&dada;&dadb;&dadc;&dadd;&dade;&dadf;&dadg;&dadh;&dadi;&daea;&daeb;&daec;&daed;&daee;&daef;&daeg;&daeh;&daei;&dafa;&dafb;&dafc;&dafd;&dafe;&daff;&dafg;&dafh;&dafi;&daga;&dagb;&dagc;&dagd;&dage;&dagf;&dagg;&dagh;&dagi;&daha;&dahb;&dahc;&dahd;&dahe;&dahf;&dahg;&dahh;&dahi;&daia;&daib;&daic;&daid;&daie;&daif;&daig;&daih;&daii;&dbaa;&dbab;&dbac;&dbad;&dbae;&dbaf;&dbag;&dbah;&dbai;&dbba;&dbbb;&dbbc;&dbbd;&dbbe;&dbbf;&dbbg;&dbbh;&dbbi;&dbca;&dbcb;&dbcc;&dbcd;&dbce;&dbcf;&dbcg;&dbch;&dbci;&dbda;&dbdb;&dbdc;&dbdd;&dbde;&dbdf;&dbdg;&dbdh;&dbdi;&dbea;&dbeb;&dbec;&dbed;&dbee;&dbef;&dbeg;&dbeh;&dbei;&dbfa;&dbfb;&dbfc;&dbfd;&dbfe;&dbff;&dbfg;&dbfh;&dbfi;&dbga;&dbgb;&dbgc;&dbgd;&dbge;&dbgf;&dbgg;&dbgh;&dbgi;&dbha;&dbhb;&dbhc;&dbhd;&dbhe;&dbhf;&dbhg;&dbhh;&dbhi;&dbia;&dbib;&dbic;&dbid;&dbie;&dbif;&dbig;&dbih;&dbii;&dcaa;&dcab;&dcac;&dcad;&dcae;&dcaf;&dcag;&dcah;&dcai;&dcba;&dcbb;&dcbc;&dcbd;&dcbe;&dcbf;&dcbg;&dcbh;&dcbi;&dcca;&dccb;&dccc;&dccd;&dcce;&dccf;&dccg;&dcch;&dcci;&dcda;&dcdb;&dcdc;&dcdd;&dcde;&dcdf;&dcdg;&dcdh;&dcdi;&dcea;&dceb;&dcec;&dced;&dcee;&dcef;&dceg;&dceh;&dcei;&dcfa;&dcfb;&dcfc;&dcfd;&dcfe;&dcff;&dcfg;&dcfh;&dcfi;&dcga;&dcgb;&dcgc;&dcgd;&dcge;&dcgf;&dcgg;&dcgh;&dcgi;&dcha;&dchb;&dchc;&dchd;&dche;&dchf;&dchg;&dchh;&dchi;&dcia;&dcib;&dcic;&dcid;&dcie;&dcif;&dcig;&dcih;&dcii;&ddaa;&ddab;&ddac;&ddad;&ddae;&ddaf;&ddag;&ddah;&ddai;&ddba;&ddbb;&ddbc;&ddbd;&ddbe;&ddbf;&ddbg;&ddbh;&ddbi;&ddca;&ddcb;&ddcc;&ddcd;&ddce;&ddcf;&ddcg;&ddch;&ddci;&ddda;&dddb;&dddc;&dddd; + + From 9fdda9932e6420bb6a2b780107f39a6fa69fef99 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Mon, 20 Jan 2020 18:24:41 -0800 Subject: [PATCH 339/774] Update Travis JDK builds --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93cfb53e67..0001f9ee4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,10 @@ env: # You can see the list of supported environments by installing Jabba and using ls-remote: # https://github.com/shyiko/jabba#usage - JDK="openjdk-ri@1.7.75" - - JDK="adopt@1.8.212-04" - - JDK="amazon-corretto@1.8.212-04.2" + - JDK="adopt@1.8.0-232" - JDK="openjdk@1.9.0-4" - JDK="1.12.0-1" + - JDK="1.13.0" cache: directories: From 86d69ea625edc3650b50b8f13364c58a173fb9bf Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 26 Jan 2020 15:26:47 -0800 Subject: [PATCH 340/774] Update default max body to 2MB. Update default UA. --- CHANGES | 4 ++++ src/main/java/org/jsoup/Connection.java | 6 ++++-- src/main/java/org/jsoup/helper/HttpConnection.java | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b2eb976d6e..4673c1d025 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,10 @@ jsoup changelog * Improvement: ensure HTTP keepalives work when fetching content via body() and bodyAsBytes(). + * Improvement: set the default max body size in Jsoup.Connection to 2MB (up from 1MB) so fewer people get trimmed + content if they have not set it, but still in sensible bounds. Also updated the default user-agent to improve + default compatibility. + * Bugfix: on pages fetch by Jsoup.Connection, a "Mark Invalid" exception might be incorrectly thrown, or the page may miss some data. This occurred on larger pages when the file transfer was chunked, an an invalid HTML entity happened to cross a chunk boundary. diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 9f4fe63fdd..12d4a615da 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -98,8 +98,10 @@ public final boolean hasBody() { /** * Set the maximum bytes to read from the (uncompressed) connection into the body, before the connection is closed, - * and the input truncated. The default maximum is 1MB. A max size of zero is treated as an infinite amount (bounded - * only by your patience and the memory available on your machine). + * and the input truncated (i.e. the body content will be trimmed). The default maximum is 2MB. A max size of + * 0 is treated as an infinite amount (bounded only by your patience and the memory available on your + * machine). + * * @param bytes number of bytes to read from the input before truncating * @return this Connection, for chaining */ diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index aad10ab770..ff51252eea 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -57,7 +57,7 @@ public class HttpConnection implements Connection { * vs in jsoup, which would otherwise default to {@code Java}. So by default, use a desktop UA. */ public static final String DEFAULT_UA = - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36"; + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"; private static final String USER_AGENT = "User-Agent"; public static final String CONTENT_TYPE = "Content-Type"; public static final String MULTIPART_FORM_DATA = "multipart/form-data"; @@ -551,7 +551,7 @@ public static class Request extends HttpConnection.Base impl Request() { timeoutMilliseconds = 30000; // 30 seconds - maxBodySizeBytes = 1024 * 1024; // 1MB + maxBodySizeBytes = 1024 * 1024 * 2; // 2MB followRedirects = true; data = new ArrayList<>(); method = Method.GET; From de97030ff54ee0bd306cbc58bd8093645cc8a5dc Mon Sep 17 00:00:00 2001 From: Csaba Varga Date: Tue, 21 Jan 2020 19:32:03 +0100 Subject: [PATCH 341/774] Fix edge cases with delimiters being near a buffer boundary. --- .../org/jsoup/parser/CharacterReader.java | 10 +++- src/main/java/org/jsoup/parser/Tokeniser.java | 5 ++ .../java/org/jsoup/parser/TokeniserState.java | 14 ++++-- .../org/jsoup/parser/CharacterReaderTest.java | 10 +++- .../java/org/jsoup/parser/TokeniserTest.java | 47 +++++++++++++++++-- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 515dcc729f..16f742ee07 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -217,8 +217,16 @@ String consumeTo(String seq) { String consumed = cacheString(charBuf, stringCache, bufPos, offset); bufPos += offset; return consumed; - } else { + } else if (bufLength - bufPos < seq.length()) { + // nextIndexOf() did a bufferUp(), so if the buffer is shorter than the search string, we must be at EOF return consumeToEnd(); + } else { + // the string we're looking for may be straddling a buffer boundary, so keep (length - 1) characters + // unread in case they contain the beginning of the search string + int endPos = bufLength - seq.length() + 1; + String consumed = cacheString(charBuf, stringCache, bufPos, endPos - bufPos); + bufPos = endPos; + return consumed; } } diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index b20dd6b191..c5e4bc203f 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -234,6 +234,11 @@ void emitCommentPending() { emit(commentPending); } + void createBogusCommentPending() { + commentPending.reset(); + commentPending.bogus = true; + } + void createDoctypePending() { doctypePending.reset(); } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index bb21d0f6e5..544c0b5892 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -105,6 +105,7 @@ void read(Tokeniser t, CharacterReader r) { t.advanceTransition(EndTagOpen); break; case '?': + t.createBogusCommentPending(); t.advanceTransition(BogusComment); break; default: @@ -134,6 +135,7 @@ void read(Tokeniser t, CharacterReader r) { t.advanceTransition(Data); } else { t.error(this); + t.createBogusCommentPending(); t.advanceTransition(BogusComment); } } @@ -910,12 +912,13 @@ void read(Tokeniser t, CharacterReader r) { // todo: handle bogus comment starting from eof. when does that trigger? // rewind to capture character that lead us here r.unconsume(); - Token.Comment comment = new Token.Comment(); - comment.bogus = true; - comment.data.append(r.consumeTo('>')); + t.commentPending.data.append(r.consumeTo('>')); // todo: replace nullChar with replaceChar - t.emit(comment); - t.advanceTransition(Data); + char next = r.consume(); + if (next == '>' || next == eof) { + t.emitCommentPending(); + t.transition(Data); + } } }, MarkupDeclarationOpen { @@ -933,6 +936,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(CdataSection); } else { t.error(this); + t.createBogusCommentPending(); t.advanceTransition(BogusComment); // advance so this character gets in bogus comment data's rewind } } diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 9323f54023..1ec8c59167 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -133,7 +133,15 @@ public class CharacterReaderTest { assertEquals('T', r.consume()); assertEquals("wo ", r.consumeTo("Two")); assertEquals('T', r.consume()); - assertEquals("wo Four", r.consumeTo("Qux")); + // To handle strings straddling across buffers, consumeTo() may return the + // data in multiple pieces near EOF. + StringBuilder builder = new StringBuilder(); + String part; + do { + part = r.consumeTo("Qux"); + builder.append(part); + } while (!part.isEmpty()); + assertEquals("wo Four", builder.toString()); } @Test public void advance() { diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index 2a2aa57401..ca80e30823 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -1,20 +1,24 @@ package org.jsoup.parser; +import static org.jsoup.parser.CharacterReader.maxBufferLen; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.UnsupportedEncodingException; +import java.util.Arrays; + import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.CDataNode; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import org.junit.Test; -import static org.jsoup.parser.CharacterReader.maxBufferLen; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - public class TokeniserTest { @Test public void bufferUpInAttributeVal() { @@ -180,4 +184,37 @@ public void bufferUpInAttributeVal() { assertEquals("At: " + i, s.charAt(0), Tokeniser.win1252Extensions[i]); } } + + @Test public void canParseVeryLongBogusComment() { + StringBuilder commentData = new StringBuilder(maxBufferLen); + do { + commentData.append("blah blah blah blah "); + } while (commentData.length() < maxBufferLen); + String expectedCommentData = commentData.toString(); + String testMarkup = ""; + Parser parser = new Parser(new HtmlTreeBuilder()); + + Document doc = parser.parseInput(testMarkup, ""); + + Node commentNode = doc.body().childNode(0); + assertTrue("Expected comment node", commentNode instanceof Comment); + assertEquals(expectedCommentData, ((Comment)commentNode).getData()); + } + + @Test public void canParseCdataEndingAtEdgeOfBuffer() { + String cdataStart = ""; + int bufLen = maxBufferLen - cdataStart.length() - 1; // also breaks with -2, but not with -3 or 0 + char[] cdataContentsArray = new char[bufLen]; + Arrays.fill(cdataContentsArray, 'x'); + String cdataContents = new String(cdataContentsArray); + String testMarkup = cdataStart + cdataContents + cdataEnd; + Parser parser = new Parser(new HtmlTreeBuilder()); + + Document doc = parser.parseInput(testMarkup, ""); + + Node cdataNode = doc.body().childNode(0); + assertTrue("Expected CDATA node", cdataNode instanceof CDataNode); + assertEquals(cdataContents, ((CDataNode)cdataNode).text()); + } } From c2b1fe73129e9c23d93e5393ce8007995c1630dc Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 26 Jan 2020 16:30:41 -0800 Subject: [PATCH 342/774] Report the correct error position in some malformed constructs Merges #1253 Fixes #1251 Authored bt @csaboka, but I rewrote the test file to fix the diff up. --- .../org/jsoup/parser/CharacterReader.java | 2 +- .../java/org/jsoup/parser/TokeniserState.java | 10 ++--- src/test/java/org/jsoup/parser/ParserIT.java | 23 ++++++++++ .../org/jsoup/parser/TokeniserStateTest.java | 43 +++++++++++++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/jsoup/parser/ParserIT.java diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 16f742ee07..d119803c76 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -16,7 +16,7 @@ public final class CharacterReader { static final char EOF = (char) -1; private static final int maxStringCacheLen = 12; static final int maxBufferLen = 1024 * 32; // visible for testing - private static final int readAheadLimit = (int) (maxBufferLen * 0.75); + static final int readAheadLimit = (int) (maxBufferLen * 0.75); // visible for testing private static final int minReadAheadLen = 1024; // the minimum mark length supported. No HTML entities can be larger than this. private final char[] charBuf; diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 544c0b5892..ab14f1d5da 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -161,8 +161,8 @@ void read(Tokeniser t, CharacterReader r) { t.transition(SelfClosingStartTag); break; case '<': // NOTE: out of spec, but clear author intent - t.error(this); r.unconsume(); + t.error(this); // intended fall through to next > case '>': t.emitTagPending(); @@ -572,17 +572,17 @@ void read(Tokeniser t, CharacterReader r) { t.transition(SelfClosingStartTag); break; case '<': // NOTE: out of spec, but clear (spec has this as a part of the attribute name) - t.error(this); r.unconsume(); + t.error(this); // intended fall through as if > case '>': t.emitTagPending(); t.transition(Data); break; case nullChar: + r.unconsume(); t.error(this); t.tagPending.newAttribute(); - r.unconsume(); t.transition(AttributeName); break; case eof: @@ -880,8 +880,8 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.error(this); r.unconsume(); + t.error(this); t.transition(BeforeAttributeName); } @@ -901,8 +901,8 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.error(this); r.unconsume(); + t.error(this); t.transition(BeforeAttributeName); } } diff --git a/src/test/java/org/jsoup/parser/ParserIT.java b/src/test/java/org/jsoup/parser/ParserIT.java new file mode 100644 index 0000000000..ca0589d3c7 --- /dev/null +++ b/src/test/java/org/jsoup/parser/ParserIT.java @@ -0,0 +1,23 @@ +package org.jsoup.parser; + +import org.junit.Test; + +/** + * Longer running Parser tests. + */ + +public class ParserIT { + @Test + public void testIssue1251() { + // https://github.com/jhy/jsoup/issues/1251 + StringBuilder str = new StringBuilder("

    Two
    ", TextUtil.stripNewlines(doc.body().html())); } + + @Test + public void testUnconsumeAtBufferBoundary() { + String triggeringSnippet = "
    Date: Sun, 26 Jan 2020 16:31:59 -0800 Subject: [PATCH 343/774] Changes for #1253 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 4673c1d025..635c525eec 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,10 @@ jsoup changelog * Bugfix: don't submit input type=button form elements. + * Bugfix: handle error position reporting correctly and don't blow up in some edge cases. + + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] From 8371a171fcc3314a343223d83bcab7fa2c40c0c7 Mon Sep 17 00:00:00 2001 From: Michel Petrovic Date: Fri, 22 Nov 2019 16:55:59 +0100 Subject: [PATCH 344/774] fix for bug #1279 --- src/main/java/org/jsoup/internal/Normalizer.java | 4 ++++ src/main/java/org/jsoup/select/Evaluator.java | 14 ++++++++++---- src/test/java/org/jsoup/select/SelectorTest.java | 7 +++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/internal/Normalizer.java b/src/main/java/org/jsoup/internal/Normalizer.java index d50433751e..305a0102bb 100644 --- a/src/main/java/org/jsoup/internal/Normalizer.java +++ b/src/main/java/org/jsoup/internal/Normalizer.java @@ -14,4 +14,8 @@ public static String lowerCase(final String input) { public static String normalize(final String input) { return lowerCase(input).trim(); } + + public static String normalize(final String input, boolean isStringLiteral) { + return isStringLiteral ? lowerCase(input) : normalize(input); + } } diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 0177d739f3..fc38cf67fd 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -217,7 +217,7 @@ public String toString() { */ public static final class AttributeWithValueStarting extends AttributeKeyPair { public AttributeWithValueStarting(String key, String value) { - super(key, value); + super(key, value, false); } @Override @@ -304,15 +304,21 @@ public abstract static class AttributeKeyPair extends Evaluator { String value; public AttributeKeyPair(String key, String value) { + this(key, value, true); + } + + public AttributeKeyPair(String key, String value, boolean trimValue) { Validate.notEmpty(key); Validate.notEmpty(value); this.key = normalize(key); - if (value.startsWith("\"") && value.endsWith("\"") - || value.startsWith("'") && value.endsWith("'")) { + boolean isStringLiteral = value.startsWith("'") && value.endsWith("'") + || value.startsWith("\"") && value.endsWith("\""); + if (isStringLiteral) { value = value.substring(1, value.length()-1); } - this.value = normalize(value); + + this.value = trimValue ? normalize(value) : normalize(value, isStringLiteral); } } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 8dc5655902..0b8b81cd79 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -814,4 +814,11 @@ public void selectClassWithSpace() { assertEquals(1, els.size()); assertEquals("Two", els.text()); } + + @Test public void startswithBeginsWithSpace() { + Document doc = Jsoup.parse("(jdvp@fct.unl.pt)"); + Elements els = doc.select("a[href^=' mailto']"); + + assertEquals(1, els.size()); + } } From a4210b7a958aa2eaff2b78881f5a1e16f8265dc8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 26 Jan 2020 16:53:41 -0800 Subject: [PATCH 345/774] Also add fix for ends with spaces Fixes #1279 --- CHANGES | 3 +++ src/main/java/org/jsoup/select/Evaluator.java | 2 +- src/test/java/org/jsoup/select/SelectorTest.java | 11 +++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 635c525eec..441f92a47c 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,9 @@ jsoup changelog + * Bugfix: handle the ^= (starts with) selector correctly when the prefix starts with a space. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index fc38cf67fd..14ef6d5218 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -237,7 +237,7 @@ public String toString() { */ public static final class AttributeWithValueEnding extends AttributeKeyPair { public AttributeWithValueEnding(String key, String value) { - super(key, value); + super(key, value, false); } @Override diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 0b8b81cd79..ccb7f57eb2 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -815,10 +815,17 @@ public void selectClassWithSpace() { assertEquals("Two", els.text()); } - @Test public void startswithBeginsWithSpace() { - Document doc = Jsoup.parse("(jdvp@fct.unl.pt)"); + @Test public void startsWithBeginsWithSpace() { + Document doc = Jsoup.parse("(abc@def.net)"); Elements els = doc.select("a[href^=' mailto']"); assertEquals(1, els.size()); } + + @Test public void endsWithEndsWithSpaces() { + Document doc = Jsoup.parse("(abc@def.net)"); + Elements els = doc.select("a[href$='.net ']"); + + assertEquals(1, els.size()); + } } From 91ca25b341bc5ad1c364b8e7389287c45ca9df2c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 30 Jan 2020 20:51:19 -0800 Subject: [PATCH 346/774] Don't normalize away zwj or zwnj Fixes #1269, combined emojis broken --- CHANGES | 4 ++++ .../java/org/jsoup/internal/StringUtil.java | 4 ++-- .../java/org/jsoup/nodes/ElementTest.java | 21 ++++++++++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 441f92a47c..9b0c7f17f7 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,10 @@ jsoup changelog * Bugfix: handle the ^= (starts with) selector correctly when the prefix starts with a space. + * Bugfix: don't strip out zero-width-joiners (or zero-width-non-joiners- when normalizing text. That breaks combined + emoji (and other text semantics). 🤦‍♂️ + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 372e54580a..3c0e37a92b 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -132,8 +132,8 @@ public static boolean isActuallyWhitespace(int c){ } public static boolean isInvisibleChar(int c) { - return Character.getType(c) == 16 && (c == 8203 || c == 8204 || c == 8205 || c == 173); - // zero width sp, zw non join, zw join, soft hyphen + return c == 8203 || c == 173; // zero width sp, soft hyphen + // previously also included zw non join, zw join - but removing those breaks semantic meaning of text } /** diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index a41653577b..f274c33020 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1214,9 +1214,8 @@ public void testAppendTo() { } @Test public void testNormalizesInvisiblesInText() { - // return Character.getType(c) == 16 && (c == 8203 || c == 8204 || c == 8205 || c == 173); - String escaped = "This­is​one‌long‍word"; - String decoded = "This\u00ADis\u200Bone\u200Clong\u200Dword"; // browser would not display those soft hyphens / other chars, so we don't want them in the text + String escaped = "This­is​one­long­word"; + String decoded = "This\u00ADis\u200Bone\u00ADlong\u00ADword"; // browser would not display those soft hyphens / other chars, so we don't want them in the text Document doc = Jsoup.parse("

    " + escaped); Element p = doc.select("p").first(); @@ -1482,4 +1481,20 @@ public FilterResult tail(Node node, int depth) { assertSame(div, div2); } + + @Test + public void doesntDeleteZWJWhenNormalizingText() { + String text = "\uD83D\uDC69\u200D\uD83D\uDCBB\uD83E\uDD26\uD83C\uDFFB\u200D\u2642\uFE0F"; + + Document doc = Jsoup.parse("

    " + text + "

    One‍Two
    "); + Element p = doc.selectFirst("p"); + Element d = doc.selectFirst("div"); + + assertEquals(12, p.text().length()); + assertEquals(text, p.text()); + assertEquals(7, d.text().length()); + assertEquals("One\u200DTwo", d.text()); + Element found = doc.selectFirst("div:contains(One\u200DTwo)"); + assertTrue(found.hasSameValue(d)); + } } From eb7b0d785d3229a87e11a18e171d011cfe664f1e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 30 Jan 2020 20:53:06 -0800 Subject: [PATCH 347/774] Changes typo --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 9b0c7f17f7..586f078a98 100644 --- a/CHANGES +++ b/CHANGES @@ -34,10 +34,10 @@ jsoup changelog * Bugfix: handle the ^= (starts with) selector correctly when the prefix starts with a space. - * Bugfix: don't strip out zero-width-joiners (or zero-width-non-joiners- when normalizing text. That breaks combined + * Bugfix: don't strip out zero-width-joiners (or zero-width-non-joiners) when normalizing text. That breaks combined emoji (and other text semantics). 🤦‍♂️ - + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] From f2ff26b63c74c379c52da1f9c58bf328598fd2ba Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 30 Jan 2020 21:42:08 -0800 Subject: [PATCH 348/774] Fix case in-sensitivity for Tag.Endswith (namespaced elements) Fixes #1257 --- CHANGES | 4 + src/main/java/org/jsoup/select/Evaluator.java | 4 +- .../java/org/jsoup/select/QueryParser.java | 10 +- .../java/org/jsoup/select/SelectorTest.java | 98 ++++++++++++++++++- 4 files changed, 109 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 586f078a98..08fc0d5ac1 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,10 @@ jsoup changelog emoji (and other text semantics). 🤦‍♂️ + * Bugfix: Evaluator.TagEndsWith (namespaced elements) and Tag disagreed in case-sensitivity. Now correctly matches + case-insensitively. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 14ef6d5218..d7d7857558 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -47,7 +47,7 @@ public Tag(String tagName) { @Override public boolean matches(Element root, Element element) { - return (element.tagName().equalsIgnoreCase(tagName)); + return (element.normalName().equals(tagName)); } @Override @@ -69,7 +69,7 @@ public TagEndsWith(String tagName) { @Override public boolean matches(Element root, Element element) { - return (element.tagName().endsWith(tagName)); + return (element.normalName().endsWith(tagName)); } @Override diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 768f70834c..9b34d6bfdb 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -220,19 +220,21 @@ private void byClass() { } private void byTag() { - String tagName = tq.consumeElementSelector(); - + // todo - these aren't dealing perfectly with case sensitivity. For case sensitive parsers, we should also make + // the tag in the selector case-sensitive (and also attribute names). But for now, normalize (lower-case) for + // consistency - both the selector and the element tag + String tagName = normalize(tq.consumeElementSelector()); Validate.notEmpty(tagName); // namespaces: wildcard match equals(tagName) or ending in ":"+tagName if (tagName.startsWith("*|")) { - evals.add(new CombiningEvaluator.Or(new Evaluator.Tag(normalize(tagName)), new Evaluator.TagEndsWith(normalize(tagName.replace("*|", ":"))))); + evals.add(new CombiningEvaluator.Or(new Evaluator.Tag(tagName), new Evaluator.TagEndsWith(tagName.replace("*|", ":")))); } else { // namespaces: if element name is "abc:def", selector must be "abc|def", so flip: if (tagName.contains("|")) tagName = tagName.replace("|", ":"); - evals.add(new Evaluator.Tag(tagName.trim())); + evals.add(new Evaluator.Tag(tagName)); } } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index ccb7f57eb2..8e76677150 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1,10 +1,11 @@ package org.jsoup.select; +import org.jsoup.Jsoup; import org.jsoup.MultiLocaleRule; import org.jsoup.MultiLocaleRule.MultiLocaleTest; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.parser.Parser; import org.junit.Rule; import org.junit.Test; @@ -146,6 +147,46 @@ public class SelectorTest { assertEquals("2", byContains.last().id()); } + @Test public void testWildcardNamespacedXmlTag() { + Document doc = Jsoup.parse( + "
    Hello
    There", + "", Parser.xmlParser() + ); + + Elements byTag = doc.select("*|Def"); + assertEquals(2, byTag.size()); + assertEquals("1", byTag.first().id()); + assertEquals("2", byTag.last().id()); + + Elements byAttr = doc.select(".bold"); + assertEquals(1, byAttr.size()); + assertEquals("2", byAttr.last().id()); + + Elements byTagAttr = doc.select("*|Def.bold"); + assertEquals(1, byTagAttr.size()); + assertEquals("2", byTagAttr.last().id()); + + Elements byContains = doc.select("*|Def:contains(e)"); + assertEquals(2, byContains.size()); + assertEquals("1", byContains.first().id()); + assertEquals("2", byContains.last().id()); + } + + @Test public void testWildCardNamespacedCaseVariations() { + Document doc = Jsoup.parse("OneTwo", "", Parser.xmlParser()); + Elements els1 = doc.select("One|Two"); + Elements els2 = doc.select("one|two"); + Elements els3 = doc.select("Three|Four"); + Elements els4 = doc.select("three|Four"); + + assertEquals(els1, els2); + assertEquals(els3, els4); + assertEquals("One", els1.text()); + assertEquals(1, els1.size()); + assertEquals("Two", els3.text()); + assertEquals(1, els2.size()); + } + @Test @MultiLocaleTest public void testByAttributeStarting() { Document doc = Jsoup.parse("
    Hello

    There

    No

    "); Elements withData = doc.select("[^data-]"); @@ -828,4 +869,59 @@ public void selectClassWithSpace() { assertEquals(1, els.size()); } + + // https://github.com/jhy/jsoup/issues/1257 + private final String mixedCase = + "text"; + private final String lowercase = + "text"; + + @Test + public void html_mixed_case_simple_name() { + Document doc = Jsoup.parse(mixedCase, "", Parser.htmlParser()); + assertEquals(0, doc.select("mixedCase").size()); + } + + @Test + public void html_mixed_case_wildcard_name() { + Document doc = Jsoup.parse(mixedCase, "", Parser.htmlParser()); + assertEquals(1, doc.select("*|mixedCase").size()); + } + + @Test + public void html_lowercase_simple_name() { + Document doc = Jsoup.parse(lowercase, "", Parser.htmlParser()); + assertEquals(0, doc.select("lowercase").size()); + } + + @Test + public void html_lowercase_wildcard_name() { + Document doc = Jsoup.parse(lowercase, "", Parser.htmlParser()); + assertEquals(1, doc.select("*|lowercase").size()); + } + + @Test + public void xml_mixed_case_simple_name() { + Document doc = Jsoup.parse(mixedCase, "", Parser.xmlParser()); + assertEquals(0, doc.select("mixedCase").size()); + } + + @Test + public void xml_mixed_case_wildcard_name() { + Document doc = Jsoup.parse(mixedCase, "", Parser.xmlParser()); + // FIXME: should be 1, to behave in the same way as lowercase_wildcard_name. + assertEquals(1, doc.select("*|mixedCase").size()); + } + + @Test + public void xml_lowercase_simple_name() { + Document doc = Jsoup.parse(lowercase, "", Parser.xmlParser()); + assertEquals(0, doc.select("lowercase").size()); + } + + @Test + public void xml_lowercase_wildcard_name() { + Document doc = Jsoup.parse(lowercase, "", Parser.xmlParser()); + assertEquals(1, doc.select("*|lowercase").size()); + } } From de1ced99d4dae991a546534454d5f7d9dc26f0b1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 30 Jan 2020 21:48:58 -0800 Subject: [PATCH 349/774] Changes typo --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 08fc0d5ac1..16cff8fe6e 100644 --- a/CHANGES +++ b/CHANGES @@ -16,8 +16,8 @@ jsoup changelog default compatibility. * Bugfix: on pages fetch by Jsoup.Connection, a "Mark Invalid" exception might be incorrectly thrown, or the page may - miss some data. This occurred on larger pages when the file transfer was chunked, an an invalid HTML entity happened - to cross a chunk boundary. + miss some data. This occurred on larger pages when the file transfer was chunked, and an invalid HTML entity + happened to cross a chunk boundary. * Bugfix: if duplicate attributes in an element exist, retain the first vs the last attribute with the same name. Case From f5fc1bb04c224865f8eee5ed718f4ffee0e18712 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 30 Jan 2020 22:13:19 -0800 Subject: [PATCH 350/774] Don't throw an exception if a selector ends in a space, just trim it. Fixes #1274 --- CHANGES | 3 +++ src/main/java/org/jsoup/select/QueryParser.java | 2 ++ .../java/org/jsoup/select/QueryParserTest.java | 16 ++++++++++++++++ src/test/java/org/jsoup/select/SelectorTest.java | 9 +++++++++ 4 files changed, 30 insertions(+) diff --git a/CHANGES b/CHANGES index 16cff8fe6e..d68c1afb2b 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,9 @@ jsoup changelog case-insensitively. + * Bugfix: Don't throw an exception if a selector ends in a space, just trim it. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 9b34d6bfdb..872c770bbd 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -27,6 +27,8 @@ public class QueryParser { * @param query CSS query */ private QueryParser(String query) { + Validate.notEmpty(query); + query = query.trim(); this.query = query; this.tq = new TokenQueue(query); } diff --git a/src/test/java/org/jsoup/select/QueryParserTest.java b/src/test/java/org/jsoup/select/QueryParserTest.java index 0720e47c9f..f2ac60cf8a 100644 --- a/src/test/java/org/jsoup/select/QueryParserTest.java +++ b/src/test/java/org/jsoup/select/QueryParserTest.java @@ -47,4 +47,20 @@ public class QueryParserTest { @Test(expected = Selector.SelectorParseException.class) public void testParsesSingleQuoteInContains() { Evaluator parse = QueryParser.parse("p:contains(One \" One)"); } + + + @Test(expected = Selector.SelectorParseException.class) + public void exceptOnEmptySelector() { + Evaluator parse = QueryParser.parse(""); + } + + @Test(expected = Selector.SelectorParseException.class) + public void exceptOnNullSelector() { + Evaluator parse = QueryParser.parse(null); + } + + @Test public void okOnSpacesForeAndAft() { + Evaluator parse = QueryParser.parse(" span div "); + assertEquals("div :parentspan", parse.toString()); // TODO - don't really love that toString() result... + } } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 8e76677150..e700434373 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -924,4 +924,13 @@ public void xml_lowercase_wildcard_name() { Document doc = Jsoup.parse(lowercase, "", Parser.xmlParser()); assertEquals(1, doc.select("*|lowercase").size()); } + + @Test + public void trimSelector() { + // https://github.com/jhy/jsoup/issues/1274 + Document doc = Jsoup.parse("

    Hello"); + Elements els = doc.select(" p span "); + assertEquals(1, els.size()); + assertEquals("Hello", els.first().text()); + } } From 5a570cf31e078c9b3865f93a37fbbde68c9799df Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 1 Feb 2020 18:32:43 -0800 Subject: [PATCH 351/774] Much faster perf when bulk inserting a complete node list into anothe r node Fixes #1281 --- CHANGES | 3 + src/main/java/org/jsoup/nodes/Element.java | 1 + src/main/java/org/jsoup/nodes/LeafNode.java | 5 ++ src/main/java/org/jsoup/nodes/Node.java | 45 +++++++++++- .../java/org/jsoup/nodes/ElementTest.java | 69 +++++++++++++++++++ 5 files changed, 120 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index d68c1afb2b..7f8c7c2a26 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,9 @@ jsoup changelog content if they have not set it, but still in sensible bounds. Also updated the default user-agent to improve default compatibility. + * Improvement: dramatic speed improvement when bulk inserting child nodes into an element (wrapping contents). + + * Bugfix: on pages fetch by Jsoup.Connection, a "Mark Invalid" exception might be incorrectly thrown, or the page may miss some data. This occurred on larger pages when the file transfer was chunked, and an invalid HTML entity happened to cross a chunk boundary. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 4a7403281f..463073fe7d 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -613,6 +613,7 @@ public Element after(Node node) { * Remove all of the element's child nodes. Any attributes are left as-is. * @return this element */ + @Override public Element empty() { childNodes.clear(); return this; diff --git a/src/main/java/org/jsoup/nodes/LeafNode.java b/src/main/java/org/jsoup/nodes/LeafNode.java index ea3605ddc3..69f2af496f 100644 --- a/src/main/java/org/jsoup/nodes/LeafNode.java +++ b/src/main/java/org/jsoup/nodes/LeafNode.java @@ -91,6 +91,11 @@ public int childNodeSize() { return 0; } + @Override + public Node empty() { + return this; + } + @Override protected List ensureChildNodes() { return EmptyNodes; diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index fbe47d6cfa..29f3edcdc2 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -1,14 +1,19 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; /** The base, abstract Node model. Elements, Documents, Comments etc are all Node instances. @@ -233,6 +238,13 @@ protected Node[] childNodesAsArray() { return ensureChildNodes().toArray(new Node[0]); } + /** + * Delete all this node's children. + * @return this node, for chaining + */ + public abstract Node empty(); + + /** Gets this node's parent node. @return parent node; or null if no parent. @@ -454,9 +466,36 @@ protected void addChildren(Node... children) { } protected void addChildren(int index, Node... children) { - Validate.noNullElements(children); + Validate.notNull(children); + if (children.length == 0) { + return; + } final List nodes = ensureChildNodes(); + // fast path - if used as a wrap (index=0, children = child[0].parent.children - do inplace + final Node firstParent = children[0].parent(); + if (firstParent != null && firstParent.childNodeSize() == children.length) { + boolean sameList = true; + final List firstParentNodes = firstParent.childNodes(); + // identity check contents to see if same + int i = children.length; + while (i-- > 0) { + if (children[i] != firstParentNodes.get(i)) { + sameList = false; + break; + } + } + firstParent.empty(); + nodes.addAll(index, Arrays.asList(children)); + i = children.length; + while (i-- > 0) { + children[i].parentNode = this; + } + reindexChildren(index); + return; + } + + Validate.noNullElements(children); for (Node child : children) { reparentChild(child); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index f274c33020..5c110ba8a2 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1497,4 +1497,73 @@ public void doesntDeleteZWJWhenNormalizingText() { Element found = doc.selectFirst("div:contains(One\u200DTwo)"); assertTrue(found.hasSameValue(d)); } + + + @Test + public void testFastReparent() { + StringBuilder htmlBuf = new StringBuilder(); + int rows = 300000; + for (int i = 1; i <= rows; i++) { + htmlBuf + .append("

    El-") + .append(i) + .append("

    "); + } + String html = htmlBuf.toString(); + Document doc = Jsoup.parse(html); + long start = System.currentTimeMillis(); + + Element wrapper = new Element("div"); + List childNodes = doc.body().childNodes(); + wrapper.insertChildren(0, childNodes); + + long runtime = System.currentTimeMillis() - start; + assertEquals(rows, wrapper.childNodes.size()); + assertEquals(0, childNodes.size()); // all moved out + + doc.body().empty().appendChild(wrapper); + Element wrapperAcutal = doc.body().children().get(0); + assertEquals(wrapper, wrapperAcutal); + assertEquals("El-1", wrapperAcutal.children().get(0).text()); + assertEquals("El-" + rows, wrapperAcutal.children().get(rows - 1).text()); + assertTrue(runtime <= 1000); + } + + @Test + public void testFastReparentExistingContent() { + StringBuilder htmlBuf = new StringBuilder(); + int rows = 300000; + for (int i = 1; i <= rows; i++) { + htmlBuf + .append("

    El-") + .append(i) + .append("

    "); + } + String html = htmlBuf.toString(); + Document doc = Jsoup.parse(html); + long start = System.currentTimeMillis(); + + Element wrapper = new Element("div"); + wrapper.append("

    Prior Content

    "); + wrapper.append("

    End Content

    "); + assertEquals(2, wrapper.childNodes.size()); + + List childNodes = doc.body().childNodes(); + wrapper.insertChildren(1, childNodes); + + long runtime = System.currentTimeMillis() - start; + assertEquals(rows + 2, wrapper.childNodes.size()); + assertEquals(0, childNodes.size()); // all moved out + + doc.body().empty().appendChild(wrapper); + Element wrapperAcutal = doc.body().children().get(0); + assertEquals(wrapper, wrapperAcutal); + assertEquals("Prior Content", wrapperAcutal.children().get(0).text()); + assertEquals("El-1", wrapperAcutal.children().get(1).text()); + + assertEquals("El-" + rows, wrapperAcutal.children().get(rows).text()); + assertEquals("End Content", wrapperAcutal.children().get(rows + 1).text()); + + assertTrue(runtime <= 1000); + } } From cfd1362861093f5836513fb1a7a9670f9c5c1de6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 1 Feb 2020 18:44:27 -0800 Subject: [PATCH 352/774] Extra tests --- .../java/org/jsoup/nodes/ElementTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 5c110ba8a2..5f851db274 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; import org.jsoup.select.NodeFilter; @@ -1566,4 +1567,26 @@ public void testFastReparentExistingContent() { assertTrue(runtime <= 1000); } + + @Test + public void testReparentSeperateNodes() { + String html = "

    One

    Two"; + Document doc = Jsoup.parse(html); + Element new1 = new Element("p").text("Three"); + Element new2 = new Element("p").text("Four"); + + doc.body().insertChildren(-1, new1, new2); + assertEquals("

    One

    Two

    Three

    Four

    ", TextUtil.stripNewlines(doc.body().html())); + + // note that these get moved from the above - as not copied + doc.body().insertChildren(0, new1, new2); + assertEquals("

    Three

    Four

    One

    Two

    ", TextUtil.stripNewlines(doc.body().html())); + + doc.body().insertChildren(0, new2.clone(), new1.clone()); + assertEquals("

    Four

    Three

    Three

    Four

    One

    Two

    ", TextUtil.stripNewlines(doc.body().html())); + + // shifted to end + doc.body().appendChild(new1); + assertEquals("

    Four

    Three

    Four

    One

    Two

    Three

    ", TextUtil.stripNewlines(doc.body().html())); + } } From a8d431bad9575d5f32d7b87252962a8aae02d59f Mon Sep 17 00:00:00 2001 From: krystiangorecki <10375973+krystiangorecki@users.noreply.github.com> Date: Sun, 2 Feb 2020 04:04:14 +0100 Subject: [PATCH 353/774] Fixes #1220 self-closing textarea --- CHANGES | 3 +++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 12 +++++++----- .../jsoup/parser/HtmlTreeBuilderStateTest.java | 15 +++++++++++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 7f8c7c2a26..87ad96ab36 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,9 @@ jsoup changelog * Bugfix: Don't throw an exception if a selector ends in a space, just trim it. + + * Bugfix: HTML parser adds redundant text when parsing self-closing textarea. + * Various code hygiene updates. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index dcde12e1be..c1fa78a13d 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -499,11 +499,13 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.processEndTag("form"); } else if (name.equals("textarea")) { tb.insert(startTag); - // todo: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.) - tb.tokeniser.transition(TokeniserState.Rcdata); - tb.markInsertionMode(); - tb.framesetOk(false); - tb.transition(Text); + if (!startTag.isSelfClosing()) { + // todo: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.) + tb.tokeniser.transition(TokeniserState.Rcdata); + tb.markInsertionMode(); + tb.framesetOk(false); + tb.transition(Text); + } } else if (name.equals("xmp")) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 953dd14e42..5edc82066d 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -1,12 +1,15 @@ package org.jsoup.parser; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.jsoup.parser.HtmlTreeBuilderState.Constants; import org.junit.Test; import java.util.Arrays; -import static org.junit.Assert.assertArrayEquals; - public class HtmlTreeBuilderStateTest { @Test public void ensureArraysAreSorted() { @@ -40,4 +43,12 @@ public void ensureArraysAreSorted() { assertArrayEquals(array, copy); } } + + @Test + public void testSelfclosingTextareaIssue1220() { + Document doc = Jsoup.parse("
    ", TextUtil.stripNewlines(doc.body().html())); + } } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 5edc82066d..6cbf1e0ef0 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -1,15 +1,12 @@ package org.jsoup.parser; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; import org.jsoup.parser.HtmlTreeBuilderState.Constants; import org.junit.Test; import java.util.Arrays; +import static org.junit.Assert.assertArrayEquals; + public class HtmlTreeBuilderStateTest { @Test public void ensureArraysAreSorted() { @@ -44,11 +41,4 @@ public void ensureArraysAreSorted() { } } - @Test - public void testSelfclosingTextareaIssue1220() { - Document doc = Jsoup.parse("
    ", TextUtil.stripNewlines(doc.body().html())); } + + @Test + public void testNoSpuriousSpace() { + Document doc = Jsoup.parse("JustOneTwo"); + assertEquals("JustOneTwo", doc.body().html()); + assertEquals("JustOneTwo", doc.body().text()); + } + + @Test + public void testH20() { + // https://github.com/jhy/jsoup/issues/731 + String html = "H2O"; + String clean = Jsoup.clean(html, Whitelist.basic()); + assertEquals("H2O", clean); + + Document doc = Jsoup.parse(html); + assertEquals("H2O", doc.text()); + } } diff --git a/src/test/java/org/jsoup/parser/TagTest.java b/src/test/java/org/jsoup/parser/TagTest.java index a10512ef12..507a08e045 100644 --- a/src/test/java/org/jsoup/parser/TagTest.java +++ b/src/test/java/org/jsoup/parser/TagTest.java @@ -64,7 +64,7 @@ public class TagTest { Tag foo2 = Tag.valueOf("FOO"); assertEquals(foo, foo2); - assertFalse(foo.isInline()); + assertTrue(foo.isInline()); assertTrue(foo.formatAsBlock()); } From 97758e74cbd39552f711480a4d1e4e5fe058efea Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 09:29:32 -0800 Subject: [PATCH 359/774] Test for #851 --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 27d726338a..4865069322 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1276,4 +1276,15 @@ public void testH20() { Document doc = Jsoup.parse(html); assertEquals("H2O", doc.text()); } + + @Test + public void testUNewlines() { + // https://github.com/jhy/jsoup/issues/851 + String html = "test on fire"; + String clean = Jsoup.clean(html, Whitelist.basic()); + assertEquals("test on fire", clean); + + Document doc = Jsoup.parse(html); + assertEquals("test on fire", doc.text()); + } } From 128008e29c33cf6b3b20fde8f2bbd13ead98509c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 09:56:10 -0800 Subject: [PATCH 360/774] Fix TextNode.outerHtml normalization for orhpans Fixes #1309 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/TextNode.java | 3 +-- .../java/org/jsoup/nodes/TextNodeTest.java | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 73818c5877..b3469686be 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,9 @@ jsoup changelog + * Bugfix: TextNode.outerHtml() wouldn't normalize correctly without a parent. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index fcd81e1a6d..5f195a2195 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -97,8 +97,7 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr if (out.prettyPrint() && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) indent(accum, depth, out); - boolean normaliseWhite = out.prettyPrint() && parent() instanceof Element - && !Element.preserveWhitespace(parent()); + boolean normaliseWhite = out.prettyPrint() && !Element.preserveWhitespace(parent()); Entities.escape(accum, coreValue(), out, false, normaliseWhite, false); } diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index 1857c0567b..c6df0c2d3b 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -82,4 +82,27 @@ public class TextNodeTest { List nodes = tn.childNodes(); assertEquals(0, nodes.size()); } + + @Test public void testSpaceNormalise() { + // https://github.com/jhy/jsoup/issues/1309 + String whole = "Two spaces"; + String norm = "Two spaces"; + TextNode tn = new TextNode(whole); // there are 2 spaces between the words + assertEquals(whole, tn.getWholeText()); + assertEquals(norm, tn.text()); + assertEquals(norm, tn.outerHtml()); + assertEquals(norm, tn.toString()); + + Element el = new Element("p"); + el.appendChild(tn); // this used to change the context + //tn.setParentNode(el); // set any parent + assertEquals(whole, tn.getWholeText()); + assertEquals(norm, tn.text()); + assertEquals(norm, tn.outerHtml()); + assertEquals(norm, tn.toString()); + + assertEquals("

    " + norm + "

    ", el.outerHtml()); + assertEquals(norm, el.html()); + assertEquals(whole, el.wholeText()); + } } From 5359490c56501b231d0d1d19e7eed4ffb37425d7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 10:11:46 -0800 Subject: [PATCH 361/774] Removed binary input detection attempt Fixes #1250 --- CHANGES | 3 ++ .../org/jsoup/parser/CharacterReader.java | 21 ------------ .../org/jsoup/integration/ConnectTest.java | 33 ------------------- .../java/org/jsoup/integration/ParseTest.java | 14 -------- 4 files changed, 3 insertions(+), 68 deletions(-) diff --git a/CHANGES b/CHANGES index b3469686be..82a7343ddd 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,9 @@ jsoup changelog * Bugfix: TextNode.outerHtml() wouldn't normalize correctly without a parent. + * Bugfix: Removed binary input detection as it was causing too many false positives. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index d119803c76..f1587a9ae2 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -34,10 +34,6 @@ public CharacterReader(Reader input, int sz) { reader = input; charBuf = new char[sz > maxBufferLen ? maxBufferLen : sz]; bufferUp(); - - if (isBinary()) { - throw new UncheckedIOException("Input is binary and unsupported"); - } } public CharacterReader(Reader input) { @@ -481,23 +477,6 @@ boolean containsIgnoreCase(String seq) { return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1); } - private static final int numNullsConsideredBinary = 10; // conservative - - /** - * Heuristic to determine if the current buffer looks like binary content. Reader will already hopefully be - * decoded correctly, so a bunch of NULLs indicates a binary file - */ - boolean isBinary() { - int nullsSeen = 0; - - for (int i = bufPos; i < bufLength; i++) { - if (charBuf[i] == '\0') - nullsSeen++; - } - - return nullsSeen >= numNullsConsideredBinary; - } - @Override public String toString() { return new String(charBuf, bufPos, bufLength - bufPos); diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 2d77e9af1b..59d55098b7 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -427,39 +427,6 @@ public void handlesEmtpyStreamDuringBufferedRead() throws IOException { assertEquals("OK", doc.title()); } - @Test - public void testBinaryThrowsExceptionWhenTypeIgnored() { - Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")); - con.data(FileServlet.ContentTypeParam, "image/jpeg"); - con.ignoreContentType(true); - - boolean threw = false; - try { - con.execute(); - Document doc = con.response().parse(); - } catch (IOException e) { - threw = true; - assertEquals("Input is binary and unsupported", e.getMessage()); - } - assertTrue(threw); - } - - @Test - public void testBinaryResultThrows() { - Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")); - con.data(FileServlet.ContentTypeParam, "text/html"); - - boolean threw = false; - try { - con.execute(); - Document doc = con.response().parse(); - } catch (IOException e) { - threw = true; - assertEquals("Input is binary and unsupported", e.getMessage()); - } - assertTrue(threw); - } - @Test public void testBinaryContentTypeThrowsException() { Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")); diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index c133649b26..0bf45ba237 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -65,20 +65,6 @@ public void testGoogleSearchIpod() throws IOException { results.get(1).attr("href")); } - @Test - public void testBinaryThrowsException() throws IOException { - File in = getFile("/htmltests/thumb.jpg"); - - boolean threw = false; - try { - Document doc = Jsoup.parse(in, "UTF-8"); - } catch (IOException e) { - threw = true; - assertEquals("Input is binary and unsupported", e.getMessage()); - } - assertTrue(threw); - } - @Test public void testYahooJp() throws IOException { File in = getFile("/htmltests/yahoo-jp.html"); From 2d431412fc5a2fea8d7d4490c487e2012474011e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 10:20:56 -0800 Subject: [PATCH 362/774] Test to verify zwnj fix for Farsi #1227 --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 4865069322..8cb827bfe7 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1287,4 +1287,11 @@ public void testUNewlines() { Document doc = Jsoup.parse(html); assertEquals("test on fire", doc.text()); } + + @Test public void testFarsi() { + // https://github.com/jhy/jsoup/issues/1227 + String text = "نیمه\u200Cشب"; + Document doc = Jsoup.parse("

    " + text); + assertEquals(text, doc.text()); + } } From 88e045eb50b304ecfb909eb9088c98bb46fe7806 Mon Sep 17 00:00:00 2001 From: offa Date: Sun, 2 Feb 2020 18:54:18 +0000 Subject: [PATCH 363/774] CI builds for current Java versions (#1254) * OpenJDK 11, 12 and 13 ci builds added. * Amazon Corrretto CI build fixed. * Just look for the latest minor version of specified major version --- .travis.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0001f9ee4b..23100bba70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,13 @@ env: # List any JDK you want to build your software with. # You can see the list of supported environments by installing Jabba and using ls-remote: # https://github.com/shyiko/jabba#usage - - JDK="openjdk-ri@1.7.75" - - JDK="adopt@1.8.0-232" - - JDK="openjdk@1.9.0-4" - - JDK="1.12.0-1" - - JDK="1.13.0" + - JDK="openjdk-ri@1.7." + - JDK="adopt@1.8." + - JDK="amazon-corretto@1.8." + - JDK="openjdk@1.9." + - JDK="openjdk@1.11." + - JDK="openjdk@1.12." + - JDK="openjdk@1.13." cache: directories: From 4e0974876e256d7822769b246900eb726508ca55 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 11:09:04 -0800 Subject: [PATCH 364/774] Renamed to childrenSize for consistency --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Element.java | 9 ++++++--- src/test/java/org/jsoup/nodes/ElementTest.java | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 94d3e5242c..99634709d7 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,9 @@ jsoup changelog * Improvement: ensure HTTP keepalives work when fetching content via body() and bodyAsBytes(). + * Improvement: added Element#childrenSize() as a convenience to get the size of an element's element children. + + * Bugfix: if duplicate attributes in an element exist, retain the first vs the last attribute with the same name. Case aware (HTML case-insensitive names, XML are case-sensitive). diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index ba8d1fc753..2a3b857248 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -265,14 +265,17 @@ public Element child(int index) { } /** - * Get the number of child nodes that are elements. + * Get the number of child nodes of this element that are elements. *

    - * This method works on the same filtered list like {@link #child(int)}. + * This method works on the same filtered list like {@link #child(int)}. Use {@link #childNodes()} and {@link + * #childNodeSize()} to get the unfiltered Nodes (e.g. includes TextNodes etc.) *

    + * * @return the number of child nodes that are elements + * @see #children() * @see #child(int) */ - public int childrenCount() { + public int childrenSize() { return childElementsList().size(); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index ef93139982..e4ab574821 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1484,10 +1484,10 @@ public FilterResult tail(Node node, int depth) { } @Test - public void testChildMixedContent() { + public void testChildSizeWithMixedContent() { Document doc = Jsoup.parse("\n\n\n\n\n
    15:00sport
    "); Element row = doc.selectFirst("table tbody tr"); - assertSame(2, row.childrenCount()); - assertFalse(row.childrenCount() == row.childNodeSize()); + assertEquals(2, row.childrenSize()); + assertEquals(5, row.childNodeSize()); } } From 9297a22afcf1396c54539e0f225d048f794783ab Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 11:34:35 -0800 Subject: [PATCH 365/774] Safeclose to get any inputstreams closed first on error --- src/main/java/org/jsoup/helper/HttpConnection.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index ff51252eea..50feccc519 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -727,7 +727,7 @@ else if (methodHasBody) long startTime = System.nanoTime(); HttpURLConnection conn = createConnection(req); - Response res; + Response res = null; try { conn.connect(); if (conn.getDoOutput()) @@ -795,10 +795,8 @@ else if (methodHasBody) } else { res.byteData = DataUtil.emptyByteBuffer(); } - } catch (IOException e){ - // per Java's documentation, this is not necessary, and precludes keepalives. However in practice, - // connection errors will not be released quickly enough and can cause a too many open files error. - conn.disconnect(); + } catch (IOException e) { + if (res != null) res.safeClose(); // will be non-null if got to conn throw e; } From d36f91a47fc5e4ae8a8b172d6822e5019abf356c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 14:19:25 -0800 Subject: [PATCH 366/774] Test to verify #1208 --- src/test/java/org/jsoup/select/SelectorTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index e700434373..baaa31faf3 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -933,4 +933,14 @@ public void trimSelector() { assertEquals(1, els.size()); assertEquals("Hello", els.first().text()); } + + @Test + public void xmlWildcardNamespaceTest() { + // https://github.com/jhy/jsoup/issues/1208 + Document doc = Jsoup.parse("11112222", "", Parser.xmlParser()); + Elements select = doc.select("*|MyXmlTag"); + assertEquals(2, select.size()); + assertEquals("1111", select.get(0).text()); + assertEquals("2222", select.get(1).text()); + } } From fe92494c7716eeb04d5d4040cc43d67b8f2f4aaf Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 15:49:04 -0800 Subject: [PATCH 367/774] W3CDom#asString should emit XML Fixes #1098 --- CHANGES | 4 ++ src/main/java/org/jsoup/helper/W3CDom.java | 17 ++++++ src/test/java/org/jsoup/TextUtil.java | 5 ++ .../java/org/jsoup/helper/W3CDomTest.java | 61 ++++++++++++++++--- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index a5185c7f69..d346ebb0d9 100644 --- a/CHANGES +++ b/CHANGES @@ -64,6 +64,10 @@ jsoup changelog * Bugfix: Removed binary input detection as it was causing too many false positives. + * Bugfix: The W3CDom#asString method (that converts a Jsoup Document to a W3C DOM and outputs it as a String) would + make invalid XML output (the Transformer was switching into HTML output mode). + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index cc769eb1e2..59575b5be1 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -20,8 +20,12 @@ import javax.xml.transform.stream.StreamResult; import java.io.StringWriter; import java.util.HashMap; +import java.util.Properties; import java.util.Stack; +import static javax.xml.transform.OutputKeys.INDENT; +import static javax.xml.transform.OutputKeys.METHOD; + /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, * for integration with toolsets that use the W3C DOM. @@ -165,12 +169,25 @@ private String updateNamespaces(org.jsoup.nodes.Element el) { * @return Document as string */ public String asString(Document doc) { + return asString(doc, null); + } + + /** + * Serialize a W3C document to a String. + * + * @param doc Document + * @param properties the transformer output properties to use. See {@link Transformer#setOutputProperties(Properties)} + * @return Document as string + */ + public String asString(Document doc, Properties properties) { try { DOMSource domSource = new DOMSource(doc); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); + transformer.setOutputProperties(properties); + transformer.setOutputProperty(METHOD, "xml"); transformer.transform(domSource, result); return writer.toString(); } catch (TransformerException e) { diff --git a/src/test/java/org/jsoup/TextUtil.java b/src/test/java/org/jsoup/TextUtil.java index 2dac4b6d6d..ca49bef8da 100644 --- a/src/test/java/org/jsoup/TextUtil.java +++ b/src/test/java/org/jsoup/TextUtil.java @@ -8,8 +8,13 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextUtil { static Pattern stripper = Pattern.compile("\\r?\\n\\s*"); + static Pattern stripCRs = Pattern.compile("\\r*"); public static String stripNewlines(String text) { return stripper.matcher(text).replaceAll(""); } + + public static String stripCRs(String text) { + return stripCRs.matcher(text).replaceAll(""); + } } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index e209321ee8..038cc3c65b 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -7,15 +7,30 @@ import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Properties; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class W3CDomTest { + private static Document parseXml(String xml) { + DocumentBuilder documentBuilder = null; + try { + documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return documentBuilder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + @Test public void simpleConversion() { String html = "W3c

    Text

    What

    Text

    What" + - "" - ); + NodeList meta = wDoc.getElementsByTagName("META"); + assertEquals(0, meta.getLength()); + + String out = w3c.asString(wDoc); + String expected = "W3c

    Text

    What"; assertEquals(expected, out); + + Document roundTrip = parseXml(out); + assertEquals("Text", roundTrip.getElementsByTagName("p").item(0).getTextContent()); + + // check we can set properties + Properties properties = new Properties(); + properties.put(OutputKeys.INDENT, "yes"); + String furtherOut = w3c.asString(wDoc, properties); + String furtherExpected = + "\n" + + "\n" + + " \n" + + " W3c\n" + + " \n" + + " \n" + + "

    Text

    \n" + + " \n" + + " \n" + + " What\n" + + " \n" + + " \n" + + " \n" + + "\n"; + assertEquals(furtherExpected, TextUtil.stripCRs(furtherOut)); // on windows, DOM will write newlines as \r\n } @Test @@ -40,12 +78,15 @@ public void convertsGoogle() throws IOException { W3CDom w3c = new W3CDom(); Document wDoc = w3c.fromJsoup(doc); Node htmlEl = wDoc.getChildNodes().item(0); - assertEquals(null, htmlEl.getNamespaceURI()); + assertNull(htmlEl.getNamespaceURI()); assertEquals("html", htmlEl.getLocalName()); assertEquals("html", htmlEl.getNodeName()); String out = w3c.asString(wDoc); assertTrue(out.contains("ipod")); + + Document roundTrip = parseXml(out); + assertEquals("Images", roundTrip.getElementsByTagName("a").item(0).getTextContent()); } From 052dc1b1cac3dd4bfded785ce8c9dadc8b95f18c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 20:01:17 -0800 Subject: [PATCH 368/774] Added tests to verify #1096 Already works - but the default output dom is namespace aware, so xpath queries must be as well. --- src/main/java/org/jsoup/helper/W3CDom.java | 5 +- .../java/org/jsoup/helper/W3CDomTest.java | 106 +++++++++++++----- 2 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 59575b5be1..972866316d 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -6,6 +6,7 @@ import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import org.w3c.dom.Comment; +import org.w3c.dom.DOMConfiguration; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; @@ -23,8 +24,7 @@ import java.util.Properties; import java.util.Stack; -import static javax.xml.transform.OutputKeys.INDENT; -import static javax.xml.transform.OutputKeys.METHOD; +import static javax.xml.transform.OutputKeys.*; /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, @@ -46,6 +46,7 @@ public Document fromJsoup(org.jsoup.nodes.Document in) { factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); Document out = builder.newDocument(); + out.setXmlStandalone(true); convert(in, out); return out; } catch (ParserConfigurationException e) { diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 038cc3c65b..a318fdac70 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -12,6 +12,10 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -21,11 +25,15 @@ import static org.junit.Assert.*; public class W3CDomTest { - private static Document parseXml(String xml) { - DocumentBuilder documentBuilder = null; + + private static Document parseXml(String xml, boolean nameSpaceAware) { try { - documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - return documentBuilder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(nameSpaceAware); + DocumentBuilder documentBuilder = factory.newDocumentBuilder(); + Document dom = documentBuilder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + dom.normalizeDocument(); + return dom; } catch (Exception e) { throw new IllegalStateException(e); } @@ -42,10 +50,11 @@ public void simpleConversion() { assertEquals(0, meta.getLength()); String out = w3c.asString(wDoc); - String expected = "W3c

    Text

    What"; - assertEquals(expected, out); + String expected = "W3c

    Text

    What"; + assertEquals(expected, TextUtil.stripNewlines(out)); - Document roundTrip = parseXml(out); + String xml = w3c.asString(wDoc); + Document roundTrip = parseXml(xml, true); assertEquals("Text", roundTrip.getElementsByTagName("p").item(0).getTextContent()); // check we can set properties @@ -53,20 +62,19 @@ public void simpleConversion() { properties.put(OutputKeys.INDENT, "yes"); String furtherOut = w3c.asString(wDoc, properties); String furtherExpected = - "\n" + - "\n" + - " \n" + - " W3c\n" + - " \n" + - " \n" + - "

    Text

    \n" + - " \n" + - " \n" + - " What\n" + - " \n" + - " \n" + - " \n" + - "\n"; + "\n" + + " \n" + + " W3c\n" + + " \n" + + " \n" + + "

    Text

    \n" + + " \n" + + " \n" + + " What\n" + + " \n" + + " \n" + + " \n" + + "\n"; assertEquals(furtherExpected, TextUtil.stripCRs(furtherOut)); // on windows, DOM will write newlines as \r\n } @@ -82,14 +90,13 @@ public void convertsGoogle() throws IOException { assertEquals("html", htmlEl.getLocalName()); assertEquals("html", htmlEl.getNodeName()); - String out = w3c.asString(wDoc); - assertTrue(out.contains("ipod")); + String xml = w3c.asString(wDoc); + assertTrue(xml.contains("ipod")); - Document roundTrip = parseXml(out); + Document roundTrip = parseXml(xml, true); assertEquals("Images", roundTrip.getElementsByTagName("a").item(0).getTextContent()); } - - + @Test public void convertsGoogleLocation() throws IOException { File in = ParseTest.getFile("/htmltests/google-ipod.html"); @@ -99,10 +106,8 @@ public void convertsGoogleLocation() throws IOException { Document wDoc = w3c.fromJsoup(doc); String out = w3c.asString(wDoc); - assertEquals(doc.location(), wDoc.getDocumentURI() ); + assertEquals(doc.location(), wDoc.getDocumentURI()); } - - @Test public void namespacePreservation() throws IOException { @@ -178,7 +183,8 @@ public void handlesInvalidAttributeNames() { Document w3Doc = new W3CDom().fromJsoup(jsoupDoc); } - @Test public void treatsUndeclaredNamespaceAsLocalName() { + @Test + public void treatsUndeclaredNamespaceAsLocalName() { String html = "One"; org.jsoup.nodes.Document doc = Jsoup.parse(html); @@ -193,7 +199,47 @@ public void handlesInvalidAttributeNames() { assertNull(fb.getNamespaceURI()); assertEquals("like", fb.getLocalName()); assertEquals("fb:like", fb.getNodeName()); + } + + @Test + public void xmlnsXpathTest() throws XPathExpressionException { + W3CDom w3c = new W3CDom(); + String html = "
    hello
    "; + Document dom = w3c.fromJsoup(Jsoup.parse(html)); + NodeList nodeList = xpath(dom, "//body");// no ns, so needs no prefix + assertEquals("div", nodeList.item(0).getLocalName()); + + // default output is namespace aware, so query needs to be as well + html = "
    hello
    "; + dom = w3c.fromJsoup(Jsoup.parse(html)); + nodeList = xpath(dom, "//body"); + assertNull(nodeList); // no matches + + dom = w3c.fromJsoup(Jsoup.parse(html)); + nodeList = xpath(dom, "//*[local-name()=\"body\"]"); + assertNotNull(nodeList); + assertEquals(1, nodeList.getLength()); + assertEquals("div", nodeList.item(0).getLocalName()); + assertEquals("http://www.w3.org/1999/xhtml", nodeList.item(0).getNamespaceURI()); + assertNull(nodeList.item(0).getPrefix()); + + // get rid of the name space awareness + String xml = w3c.asString(dom); + dom = parseXml(xml, false); + Node item = (Node) xpath(dom, "//body"); + assertEquals("body", item.getNodeName()); + assertNull(item.getNamespaceURI()); + assertNull(item.getPrefix()); + + // put back, will get zero + dom = parseXml(xml, true); + nodeList = xpath(dom, "//body"); + assertNull(nodeList); + } + private NodeList xpath(Document w3cDoc, String query) throws XPathExpressionException { + XPathExpression xpath = XPathFactory.newInstance().newXPath().compile(query); + return ((NodeList) xpath.evaluate(w3cDoc, XPathConstants.NODE)); } } From 1a84ce3b0e3df1018088c59b7c4484df181a2fdb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 2 Feb 2020 20:09:55 -0800 Subject: [PATCH 369/774] Normalize xml strings before comparison --- src/test/java/org/jsoup/helper/W3CDomTest.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index a318fdac70..30691bf889 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -61,21 +61,10 @@ public void simpleConversion() { Properties properties = new Properties(); properties.put(OutputKeys.INDENT, "yes"); String furtherOut = w3c.asString(wDoc, properties); + assertTrue(furtherOut.length() > out.length()); // wanted to assert formatting, but actual indentation is platform specific so breaks in CI String furtherExpected = - "\n" + - " \n" + - " W3c\n" + - " \n" + - " \n" + - "

    Text

    \n" + - " \n" + - " \n" + - " What\n" + - " \n" + - " \n" + - " \n" + - "\n"; - assertEquals(furtherExpected, TextUtil.stripCRs(furtherOut)); // on windows, DOM will write newlines as \r\n + "W3c

    Text

    What"; + assertEquals(furtherExpected, TextUtil.stripNewlines(furtherOut)); // on windows, DOM will write newlines as \r\n } @Test From 4e052740436ce42efa1bf4cb49204f90e4796fd7 Mon Sep 17 00:00:00 2001 From: jhy Date: Wed, 5 Feb 2020 22:29:03 -0800 Subject: [PATCH 370/774] Updata Jetty servlet to current This is used for integ tests, and is not distributed with the jar. Had to update some tests - Jetty won't write past the Content Length size any more. Might add some more tests around this. --- pom.xml | 11 ++---- .../java/org/jsoup/integration/ConnectIT.java | 2 +- .../org/jsoup/integration/ConnectTest.java | 36 +++++++++---------- .../integration/servlets/EchoServlet.java | 2 +- .../servlets/InterruptedServlet.java | 9 ++++- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index a2ec63cac6..7e6803abf2 100644 --- a/pom.xml +++ b/pom.xml @@ -169,12 +169,6 @@ - - src/main/java - - **/*.properties - - ./ META-INF/ @@ -271,15 +265,16 @@ org.eclipse.jetty jetty-server - 9.2.28.v20190418 + 9.4.26.v20200117 test + org.eclipse.jetty jetty-servlet - 9.2.28.v20190418 + 9.4.26.v20200117 test diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java index 9846095e42..4a7336d566 100644 --- a/src/test/java/org/jsoup/integration/ConnectIT.java +++ b/src/test/java/org/jsoup/integration/ConnectIT.java @@ -84,7 +84,7 @@ public void totalTimeout() throws IOException { long end = System.currentTimeMillis(); long took = end - start; assertTrue(("Time taken was " + took), took > timeout); - assertTrue(("Time taken was " + took), took < timeout * 1.2); + assertTrue(("Time taken was " + took), took < timeout * 1.8); threw = true; } diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 59d55098b7..cb4bf6dd09 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -340,36 +340,32 @@ public void supportsDeflate() throws IOException { } @Test - public void handlesEmptyStreamDuringParseRead() throws IOException { + public void handlesLargerContentLengthParseRead() throws IOException { // this handles situations where the remote server sets a content length greater than it actually writes Connection.Response res = Jsoup.connect(InterruptedServlet.Url) - .timeout(200) + .data(InterruptedServlet.Magnitude, InterruptedServlet.Larger) + .timeout(400) .execute(); - boolean threw = false; - try { - Document document = res.parse(); - assertEquals("Something", document.title()); - } catch (IOException e) { - threw = true; - } - assertTrue(threw); + Document document = res.parse(); + assertEquals("Something", document.title()); + assertEquals(0, document.select("p").size()); + // current impl, jetty won't write past content length + // todo - find way to trick jetty into writing larger than set header. Take over the stream? } @Test - public void handlesEmtpyStreamDuringBufferedRead() throws IOException { + public void handlesWrongContentLengthDuringBufferedRead() throws IOException { Connection.Response res = Jsoup.connect(InterruptedServlet.Url) - .timeout(200) - .execute(); + .timeout(400) + .execute(); + // this servlet writes max_buffer data, but sets content length to max_buffer/2. So will read up to that. + // previous versions of jetty would allow to write less, and would throw except here - boolean threw = false; - try { - res.bufferUp(); - } catch (UncheckedIOException e) { - threw = true; - } - assertTrue(threw); + res.bufferUp(); + Document doc = res.parse(); + assertEquals(0, doc.select("p").size()); } @Test public void handlesRedirect() throws IOException { diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java index e3eaebd2e2..a12272a6b7 100644 --- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -122,7 +122,7 @@ private static boolean maybeEnableMultipart(HttpServletRequest req) { && req.getContentType().startsWith("multipart/form-data"); if (isMulti) { - req.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement( + req.setAttribute(Request.MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement( System.getProperty("java.io.tmpdir"))); } return isMulti; diff --git a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java index f403be9f00..298aaf1589 100644 --- a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java @@ -10,9 +10,14 @@ public class InterruptedServlet extends BaseServlet { public static final String Url = TestServer.map(InterruptedServlet.class); + public static final String Magnitude = "magnitude"; + public static final String Larger = "larger"; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + String magnitude = req.getParameter(Magnitude); + magnitude = magnitude == null ? "" : magnitude; res.setContentType(TextHtml); res.setStatus(HttpServletResponse.SC_OK); @@ -21,9 +26,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser while (sb.length() <= CharacterReaderTest.maxBufferLen) { sb.append("A suitable amount of data. \n"); } + sb.append("

    Finale.

    "); String data = sb.toString(); - res.setContentLength(data.length() * 2); + int contentLength = magnitude.equals(Larger) ? data.length() * 2 : data.length() / 2; + res.setContentLength(contentLength); res.getWriter().write(data); From 2ea6f9a8fc51b3a52754517523699b255beb5832 Mon Sep 17 00:00:00 2001 From: jhy Date: Wed, 5 Feb 2020 22:41:13 -0800 Subject: [PATCH 371/774] Test cleanup --- src/test/java/org/jsoup/select/SelectorTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index baaa31faf3..bb70d52d3b 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -261,7 +261,7 @@ public class SelectorTest { assertEquals("div", els.get(1).tagName()); assertEquals("bar", els.get(1).attr("title")); assertEquals("div", els.get(2).tagName()); - assertTrue(els.get(2).attr("title").length() == 0); // missing attributes come back as empty string + assertEquals(0, els.get(2).attr("title").length()); // missing attributes come back as empty string assertFalse(els.get(2).hasAttr("title")); assertEquals("p", els.get(3).tagName()); assertEquals("span", els.get(4).tagName()); @@ -909,7 +909,6 @@ public void xml_mixed_case_simple_name() { @Test public void xml_mixed_case_wildcard_name() { Document doc = Jsoup.parse(mixedCase, "", Parser.xmlParser()); - // FIXME: should be 1, to behave in the same way as lowercase_wildcard_name. assertEquals(1, doc.select("*|mixedCase").size()); } From 6b2bf55e43ce17efb8d8ac15d5417d78c3eaad16 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 6 Feb 2020 10:01:11 -0800 Subject: [PATCH 372/774] Added test to validate #1243 (Doesn't repro) --- src/test/java/org/jsoup/safety/CleanerTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 67fd9231f5..da0709462c 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -9,6 +9,8 @@ import org.junit.Rule; import org.junit.Test; +import javax.xml.soap.Text; + import static org.junit.Assert.*; /** @@ -312,4 +314,17 @@ public void bailsIfRemovingProtocolThatsNotSet() { String clean = Jsoup.clean(dirty, relaxedWithAnchor); assertEquals("One Two", clean); } + + @Test public void handlesNestedQuotesInAttribute() { + // https://github.com/jhy/jsoup/issues/1243 - no repro + String orig = "
    Will (not) fail
    "; + Whitelist allow = Whitelist.relaxed() + .addAttributes("div", "style"); + + String clean = Jsoup.clean(orig, allow); + boolean isValid = Jsoup.isValid(orig, allow); + + assertEquals(orig, TextUtil.stripNewlines(clean)); // only difference is pretty print wrap & indent + assertTrue(isValid); + } } From 3bd77f862826a9b3b1bd4c33a66af51cb467eea8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 6 Feb 2020 10:02:47 -0800 Subject: [PATCH 373/774] Don't want that --- src/test/java/org/jsoup/safety/CleanerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index da0709462c..26ad288841 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -9,8 +9,6 @@ import org.junit.Rule; import org.junit.Test; -import javax.xml.soap.Text; - import static org.junit.Assert.*; /** From fb50d9635874185fc050735057b8a7363cd53f3f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Thu, 6 Feb 2020 13:23:08 -0800 Subject: [PATCH 374/774] Deepclone attributes in LeafNodes if set Fixes #1176 --- CHANGES | 4 +++ src/main/java/org/jsoup/nodes/CDataNode.java | 5 ++++ src/main/java/org/jsoup/nodes/Comment.java | 5 ++++ src/main/java/org/jsoup/nodes/DataNode.java | 5 ++++ .../java/org/jsoup/nodes/FormElement.java | 5 ++++ src/main/java/org/jsoup/nodes/LeafNode.java | 11 +++++++ src/main/java/org/jsoup/nodes/TextNode.java | 5 ++++ .../java/org/jsoup/nodes/XmlDeclaration.java | 5 ++++ .../java/org/jsoup/nodes/TextNodeTest.java | 30 +++++++++++++++++++ 9 files changed, 75 insertions(+) diff --git a/CHANGES b/CHANGES index d346ebb0d9..43a00916b6 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,10 @@ jsoup changelog make invalid XML output (the Transformer was switching into HTML output mode). + * Bugfix: when cloning a TextNode, if .attributes() was hit before the clone() method, the text value would only be a + shallow clone. + + * Various code hygiene updates. **** Release 1.12.1 [2019-May-12] diff --git a/src/main/java/org/jsoup/nodes/CDataNode.java b/src/main/java/org/jsoup/nodes/CDataNode.java index 369825619e..577f7247fa 100644 --- a/src/main/java/org/jsoup/nodes/CDataNode.java +++ b/src/main/java/org/jsoup/nodes/CDataNode.java @@ -41,4 +41,9 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { throw new UncheckedIOException(e); } } + + @Override + public CDataNode clone() { + return (CDataNode) super.clone(); + } } diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index d9833854e8..5e141f2a8b 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -58,6 +58,11 @@ public String toString() { return outerHtml(); } + @Override + public Comment clone() { + return (Comment) super.clone(); + } + /** * Check if this comment looks like an XML Declaration. * @return true if it looks like, maybe, it's an XML Declaration. diff --git a/src/main/java/org/jsoup/nodes/DataNode.java b/src/main/java/org/jsoup/nodes/DataNode.java index 7981a7ea67..270d5ed114 100644 --- a/src/main/java/org/jsoup/nodes/DataNode.java +++ b/src/main/java/org/jsoup/nodes/DataNode.java @@ -59,6 +59,11 @@ public String toString() { return outerHtml(); } + @Override + public DataNode clone() { + return (DataNode) super.clone(); + } + /** Create a new DataNode from HTML encoded data. @param encodedData encoded data diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index 57702894dd..7d631fbbd0 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -112,4 +112,9 @@ public List formData() { } return data; } + + @Override + public FormElement clone() { + return (FormElement) super.clone(); + } } diff --git a/src/main/java/org/jsoup/nodes/LeafNode.java b/src/main/java/org/jsoup/nodes/LeafNode.java index 69f2af496f..2dd6542417 100644 --- a/src/main/java/org/jsoup/nodes/LeafNode.java +++ b/src/main/java/org/jsoup/nodes/LeafNode.java @@ -100,4 +100,15 @@ public Node empty() { protected List ensureChildNodes() { return EmptyNodes; } + + @Override + protected LeafNode doClone(Node parent) { + LeafNode clone = (LeafNode) super.doClone(parent); + + // Object value could be plain string or attributes - need to clone + if (hasAttributes()) + clone.value = ((Attributes) value).clone(); + + return clone; + } } diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 5f195a2195..26ee95e3c9 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -108,6 +108,11 @@ public String toString() { return outerHtml(); } + @Override + public TextNode clone() { + return (TextNode) super.clone(); + } + /** * Create a new TextNode from HTML encoded (aka escaped) data. * @param encodedText Text containing encoded HTML (e.g. &lt;) diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index d1d93058c9..2805886f76 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -89,4 +89,9 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { public String toString() { return outerHtml(); } + + @Override + public XmlDeclaration clone() { + return (XmlDeclaration) super.clone(); + } } diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index c6df0c2d3b..946dffed38 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -105,4 +105,34 @@ public class TextNodeTest { assertEquals(norm, el.html()); assertEquals(whole, el.wholeText()); } + + @Test + public void testClone() { + // https://github.com/jhy/jsoup/issues/1176 + TextNode x = new TextNode("zzz"); + TextNode y = x.clone(); + + assertNotSame(x, y); + assertEquals(x.outerHtml(), y.outerHtml()); + + y.text("yyy"); + assertNotEquals(x.outerHtml(), y.outerHtml()); + assertEquals("zzz", x.text()); + + x.attributes(); // already cloned so no impact + y.text("xxx"); + assertEquals("zzz", x.text()); + assertEquals("xxx", y.text()); + } + + @Test + public void testCloneAfterAttributesHit() { + // https://github.com/jhy/jsoup/issues/1176 + TextNode x = new TextNode("zzz"); + x.attributes(); // moves content from leafnode value to attributes, which were missed in clone + TextNode y = x.clone(); + y.text("xxx"); + assertEquals("zzz", x.text()); + assertEquals("xxx", y.text()); + } } From e02085409f0bdd7e71441fc62448192720305444 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 8 Feb 2020 16:54:29 -0800 Subject: [PATCH 375/774] In W3CDom, handle doctypes, and don't force XML output Fixes #1183 Updates #1098 --- CHANGES | 14 +- src/main/java/org/jsoup/helper/W3CDom.java | 157 +++++++++++++----- src/main/java/org/jsoup/nodes/Document.java | 15 ++ .../java/org/jsoup/nodes/DocumentType.java | 43 ++++- .../java/org/jsoup/helper/W3CDomTest.java | 74 +++++++-- .../java/org/jsoup/nodes/DocumentTest.java | 14 +- .../org/jsoup/nodes/DocumentTypeTest.java | 5 +- 7 files changed, 257 insertions(+), 65 deletions(-) diff --git a/CHANGES b/CHANGES index 43a00916b6..aa6fe21ced 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,16 @@ jsoup changelog * Improvement: added Element#childrenSize() as a convenience to get the size of an element's element children. + * Improvement: in W3CDom.asString, allow the output mode to be specified as HTML or as XML. It will default to + checking the content, and automatically selecting. + + * Improvement: added a Document#documentType() method, to get a doc's doctype. + + * Improvement: To DocumentType, added #name(), #publicID(), and #systemId() methods to fetch those fields. + + * Improvement: in W3CDom conversions from jsoup documents, retain the DocumentType, and be able to serialize it. + + * Bugfix: on pages fetch by Jsoup.Connection, a "Mark Invalid" exception might be incorrectly thrown, or the page may miss some data. This occurred on larger pages when the file transfer was chunked, and an invalid HTML entity happened to cross a chunk boundary. @@ -64,10 +74,6 @@ jsoup changelog * Bugfix: Removed binary input detection as it was causing too many false positives. - * Bugfix: The W3CDom#asString method (that converts a Jsoup Document to a W3C DOM and outputs it as a String) would - make invalid XML output (the Transformer was switching into HTML output mode). - - * Bugfix: when cloning a TextNode, if .attributes() was hit before the clone() method, the text value would only be a shallow clone. diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 972866316d..324a0d5557 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -6,14 +6,16 @@ import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import org.w3c.dom.Comment; -import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Text; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; @@ -21,20 +23,111 @@ import javax.xml.transform.stream.StreamResult; import java.io.StringWriter; import java.util.HashMap; +import java.util.Map; import java.util.Properties; import java.util.Stack; -import static javax.xml.transform.OutputKeys.*; +import static javax.xml.transform.OutputKeys.METHOD; /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, * for integration with toolsets that use the W3C DOM. */ public class W3CDom { - protected DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + protected DocumentBuilderFactory factory; + + public W3CDom() { + factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + } + + /** + * Converts a jsoup DOM to a W3C DOM + * + * @param in jsoup Document + * @return W3C Document + */ + public static Document convert(org.jsoup.nodes.Document in) { + return (new W3CDom().fromJsoup(in)); + } + + /** + * Serialize a W3C document to a String. Provide Properties to define output settings including if HTML or XML. If + * you don't provide the properties ({@code null}), the output will be auto-detected based on the content of the + * document. + * + * @param doc Document + * @param properties (optional/nullable) the output properties to use. See {@link + * Transformer#setOutputProperties(Properties)} and {@link OutputKeys} + * @return Document as string + * @see #OutputHtml + * @see #OutputXml + * @see OutputKeys#ENCODING + * @see OutputKeys#OMIT_XML_DECLARATION + * @see OutputKeys#STANDALONE + * @see OutputKeys#STANDALONE + * @see OutputKeys#DOCTYPE_PUBLIC + * @see OutputKeys#DOCTYPE_PUBLIC + * @see OutputKeys#CDATA_SECTION_ELEMENTS + * @see OutputKeys#INDENT + * @see OutputKeys#MEDIA_TYPE + */ + public static String asString(Document doc, Map properties) { + try { + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + if (properties != null) + transformer.setOutputProperties(propertiesFromMap(properties)); + + if (doc.getDoctype() != null) { + DocumentType doctype = doc.getDoctype(); + if (!StringUtil.isBlank(doctype.getPublicId())) + transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctype.getPublicId()); + if (!StringUtil.isBlank(doctype.getSystemId())) + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctype.getSystemId()); + // handle for legacy dom. TODO: nicer if + else if (doctype.getName().equalsIgnoreCase("html") + && StringUtil.isBlank(doctype.getPublicId()) + && StringUtil.isBlank(doctype.getSystemId())) + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "about:legacy-compat"); + } + + transformer.transform(domSource, result); + return writer.toString(); + + } catch (TransformerException e) { + throw new IllegalStateException(e); + } + } + + static Properties propertiesFromMap(Map map) { + Properties props = new Properties(); + props.putAll(map); + return props; + } + + /** Canned default for HTML output. */ + public static HashMap OutputHtml() { + return methodMap("html"); + } + + /** Canned default for XML output. */ + public static HashMap OutputXml() { + return methodMap("xml"); + } + + private static HashMap methodMap(String method) { + HashMap map = new HashMap<>(); + map.put(METHOD, method); + return map; + } /** * Convert a jsoup Document to a W3C Document. + * * @param in jsoup doc * @return w3c doc */ @@ -42,11 +135,18 @@ public Document fromJsoup(org.jsoup.nodes.Document in) { Validate.notNull(in); DocumentBuilder builder; try { - //set the factory to be namespace-aware - factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); - Document out = builder.newDocument(); + DOMImplementation impl = builder.getDOMImplementation(); + Document out; + + out = builder.newDocument(); + org.jsoup.nodes.DocumentType doctype = in.documentType(); + if (doctype != null) { + org.w3c.dom.DocumentType documentType = impl.createDocumentType(doctype.name(), doctype.publicId(), doctype.systemId()); + out.appendChild(documentType); + } out.setXmlStandalone(true); + convert(in, out); return out; } catch (ParserConfigurationException e) { @@ -57,6 +157,7 @@ public Document fromJsoup(org.jsoup.nodes.Document in) { /** * Converts a jsoup document into the provided W3C Document. If required, you can set options on the output document * before converting. + * * @param in jsoup doc * @param out w3c doc * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Document) @@ -69,6 +170,17 @@ public void convert(org.jsoup.nodes.Document in, Document out) { NodeTraversor.traverse(new W3CBuilder(out), rootEl); } + /** + * Serialize a W3C document to a String. The output format will be XML or HTML depending on the content of the doc. + * + * @param doc Document + * @return Document as string + * @see W3CDom#asString(Document, Map) + */ + public String asString(Document doc) { + return asString(doc, null); + } + /** * Implements the conversion by walking the input. */ @@ -118,6 +230,7 @@ public void head(org.jsoup.nodes.Node source, int depth) { dest.appendChild(node); } else { // unhandled + // not that doctype is not handled here - rather it is used in the initial doc creation } } @@ -163,36 +276,4 @@ private String updateNamespaces(org.jsoup.nodes.Element el) { } } - - /** - * Serialize a W3C document to a String. - * @param doc Document - * @return Document as string - */ - public String asString(Document doc) { - return asString(doc, null); - } - - /** - * Serialize a W3C document to a String. - * - * @param doc Document - * @param properties the transformer output properties to use. See {@link Transformer#setOutputProperties(Properties)} - * @return Document as string - */ - public String asString(Document doc, Properties properties) { - try { - DOMSource domSource = new DOMSource(doc); - StringWriter writer = new StringWriter(); - StreamResult result = new StreamResult(writer); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer.setOutputProperties(properties); - transformer.setOutputProperty(METHOD, "xml"); - transformer.transform(domSource, result); - return writer.toString(); - } catch (TransformerException e) { - throw new IllegalStateException(e); - } - } } diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index fe389a3807..92ae0b72ed 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -59,6 +59,21 @@ public static Document createShell(String baseUri) { public String location() { return location; } + + /** + * Returns this Document's doctype. + * @return document type, or null if not set + */ + public DocumentType documentType() { + for (Node node : childNodes) { + if (node instanceof DocumentType) + return (DocumentType) node; + else if (!(node instanceof LeafNode)) // scans forward across comments, text, processing instructions etc + break; + } + return null; + // todo - add a set document type? + } /** Accessor to the document's {@code head} element. diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index dbc08f37e1..b94f2c76d2 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -31,10 +31,8 @@ public DocumentType(String name, String publicId, String systemId) { Validate.notNull(systemId); attr(NAME, name); attr(PUBLIC_ID, publicId); - if (has(PUBLIC_ID)) { - attr(PUB_SYS_KEY, PUBLIC_KEY); - } attr(SYSTEM_ID, systemId); + updatePubSyskey(); } /** @@ -48,10 +46,8 @@ public DocumentType(String name, String publicId, String systemId) { public DocumentType(String name, String publicId, String systemId, String baseUri) { attr(NAME, name); attr(PUBLIC_ID, publicId); - if (has(PUBLIC_ID)) { - attr(PUB_SYS_KEY, PUBLIC_KEY); - } attr(SYSTEM_ID, systemId); + updatePubSyskey(); } /** @@ -64,17 +60,46 @@ public DocumentType(String name, String publicId, String systemId, String baseUr */ public DocumentType(String name, String pubSysKey, String publicId, String systemId, String baseUri) { attr(NAME, name); - if (pubSysKey != null) { - attr(PUB_SYS_KEY, pubSysKey); - } attr(PUBLIC_ID, publicId); attr(SYSTEM_ID, systemId); + updatePubSyskey(); } public void setPubSysKey(String value) { if (value != null) attr(PUB_SYS_KEY, value); } + private void updatePubSyskey() { + if (has(PUBLIC_ID)) { + attr(PUB_SYS_KEY, PUBLIC_KEY); + } else if (has(SYSTEM_ID)) + attr(PUB_SYS_KEY, SYSTEM_KEY); + } + + /** + * Get this doctype's name (when set, or empty string) + * @return doctype name + */ + public String name() { + return attr(NAME); + } + + /** + * Get this doctype's Public ID (when set, or empty string) + * @return doctype Public ID + */ + public String publicId() { + return attr(PUBLIC_ID); + } + + /** + * Get this doctype's System ID (when set, or empty string) + * @return doctype System ID + */ + public String systemId() { + return attr(SYSTEM_ID); + } + @Override public String nodeName() { return "#doctype"; diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 30691bf889..91c54b95fc 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -6,8 +6,12 @@ import org.jsoup.nodes.Element; import org.junit.Test; import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -19,8 +23,9 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.StringReader; import java.nio.charset.StandardCharsets; -import java.util.Properties; +import java.util.Map; import static org.junit.Assert.*; @@ -30,8 +35,19 @@ private static Document parseXml(String xml, boolean nameSpaceAware) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(nameSpaceAware); - DocumentBuilder documentBuilder = factory.newDocumentBuilder(); - Document dom = documentBuilder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(new EntityResolver() { + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + if (systemId.contains("about:legacy-compat")) { // + return new InputSource(new StringReader("")); + } else { + return null; + } + } + }); + Document dom = builder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); dom.normalizeDocument(); return dom; } catch (Exception e) { @@ -49,18 +65,17 @@ public void simpleConversion() { NodeList meta = wDoc.getElementsByTagName("META"); assertEquals(0, meta.getLength()); - String out = w3c.asString(wDoc); + String out = W3CDom.asString(wDoc, W3CDom.OutputXml()); String expected = "W3c

    Text

    What"; assertEquals(expected, TextUtil.stripNewlines(out)); - String xml = w3c.asString(wDoc); - Document roundTrip = parseXml(xml, true); + Document roundTrip = parseXml(out, true); assertEquals("Text", roundTrip.getElementsByTagName("p").item(0).getTextContent()); // check we can set properties - Properties properties = new Properties(); + Map properties = W3CDom.OutputXml(); properties.put(OutputKeys.INDENT, "yes"); - String furtherOut = w3c.asString(wDoc, properties); + String furtherOut = W3CDom.asString(wDoc, properties); assertTrue(furtherOut.length() > out.length()); // wanted to assert formatting, but actual indentation is platform specific so breaks in CI String furtherExpected = "W3c

    Text

    What"; @@ -74,12 +89,17 @@ public void convertsGoogle() throws IOException { W3CDom w3c = new W3CDom(); Document wDoc = w3c.fromJsoup(doc); - Node htmlEl = wDoc.getChildNodes().item(0); + Node htmlEl = wDoc.getChildNodes().item(1); assertNull(htmlEl.getNamespaceURI()); assertEquals("html", htmlEl.getLocalName()); assertEquals("html", htmlEl.getNodeName()); - String xml = w3c.asString(wDoc); + DocumentType doctype = wDoc.getDoctype(); + Node doctypeNode = wDoc.getChildNodes().item(0); + assertSame(doctype, doctypeNode); + assertEquals("html", doctype.getName()); + + String xml = W3CDom.asString(wDoc, W3CDom.OutputXml()); assertTrue(xml.contains("ipod")); Document roundTrip = parseXml(xml, true); @@ -230,5 +250,39 @@ private NodeList xpath(Document w3cDoc, String query) throws XPathExpressionExce XPathExpression xpath = XPathFactory.newInstance().newXPath().compile(query); return ((NodeList) xpath.evaluate(w3cDoc, XPathConstants.NODE)); } + + @Test + public void testRoundTripDoctype() { + // TODO - not super happy with this output - but plain DOM doesn't let it out, and don't want to rebuild the writer + String base = "

    One

    "; + assertEquals("

    One

    ", + output(base, true)); + assertEquals("

    One

    ", output(base, false)); + + String publicDoc = ""; + assertEquals(" ", output(publicDoc, true)); + assertEquals("", output(publicDoc, false)); + + String systemDoc = ""; + assertEquals(" ", output(systemDoc, true)); + assertEquals("", output(systemDoc, false)); + + String legacyDoc = ""; + assertEquals(" ", output(legacyDoc, true)); + assertEquals("", output(legacyDoc, false)); + + String noDoctype = "

    One

    "; + assertEquals("

    One

    ", output(noDoctype, true)); + assertEquals("

    One

    ", output(noDoctype, false)); + } + + private String output(String in, boolean modeHtml) { + org.jsoup.nodes.Document jdoc = Jsoup.parse(in); + Document w3c = W3CDom.convert(jdoc); + + Map properties = modeHtml ? W3CDom.OutputHtml() : W3CDom.OutputXml(); + return TextUtil.stripNewlines(W3CDom.asString(w3c, properties)); + } + } diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 0a56fa3553..98bd1b272c 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -100,13 +100,13 @@ public class DocumentTest { } @Test public void testLocation() throws IOException { - File in = new ParseTest().getFile("/htmltests/yahoo-jp.html"); + File in = ParseTest.getFile("/htmltests/yahoo-jp.html"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.yahoo.co.jp/index.html"); String location = doc.location(); String baseUri = doc.baseUri(); assertEquals("http://www.yahoo.co.jp/index.html",location); assertEquals("http://www.yahoo.co.jp/_ylh=X3oDMTB0NWxnaGxsBF9TAzIwNzcyOTYyNjUEdGlkAzEyBHRtcGwDZ2Ex/",baseUri); - in = new ParseTest().getFile("/htmltests/nyt-article-1.html"); + in = ParseTest.getFile("/htmltests/nyt-article-1.html"); doc = Jsoup.parse(in, null, "http://www.nytimes.com/2010/07/26/business/global/26bp.html?hp"); location = doc.location(); baseUri = doc.baseUri(); @@ -176,7 +176,7 @@ public class DocumentTest { assertFalse(docA.hashCode() == docC.hashCode()); } - @Test public void DocumentsWithSameContentAreVerifialbe() throws Exception { + @Test public void DocumentsWithSameContentAreVerifiable() throws Exception { Document docA = Jsoup.parse("
    One"); Document docB = Jsoup.parse("
    One"); Document docC = Jsoup.parse("
    Two"); @@ -454,4 +454,12 @@ public void run() { assertEquals(StandardCharsets.US_ASCII, doc.outputSettings().charset()); assertEquals(asci, p.outerHtml()); } + + @Test public void testDocumentTypeGet() { + String html = "\n\n

    One

    "; + Document doc = Jsoup.parse(html); + DocumentType documentType = doc.documentType(); + assertNotNull(documentType); + assertEquals("html", documentType.name()); + } } diff --git a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java index 7f32adb117..443feb7542 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java @@ -35,10 +35,13 @@ public void constructorValidationOkWithBlankPublicAndSystemIds() { assertEquals("", publicDocType.outerHtml()); DocumentType systemDocType = new DocumentType("html", "", "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"); - assertEquals("", systemDocType.outerHtml()); + assertEquals("", systemDocType.outerHtml()); DocumentType combo = new DocumentType("notHtml", "--public", "--system"); assertEquals("", combo.outerHtml()); + assertEquals("notHtml", combo.name()); + assertEquals("--public", combo.publicId()); + assertEquals("--system", combo.systemId()); } @Test public void testRoundTrip() { From 0bec593106b90897b8b7b8f3ac82603ca56a0b6c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 8 Feb 2020 17:05:34 -0800 Subject: [PATCH 376/774] Readme URL update --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b77b2a2a96..cfb3a1d957 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **jsoup** is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. -**jsoup** implements the [WHATWG HTML5](http://whatwg.org/html) specification, and parses HTML to the same DOM as modern browsers do. +**jsoup** implements the [WHATWG HTML5](https://html.spec.whatwg.org/multipage/) specification, and parses HTML to the same DOM as modern browsers do. * scrape and [parse](https://jsoup.org/cookbook/input/parse-document-from-string) HTML from a URL, file, or string * find and [extract data](https://jsoup.org/cookbook/extracting-data/selector-syntax), using DOM traversal or CSS selectors @@ -18,10 +18,10 @@ See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API document [![Build Status](https://travis-ci.org/jhy/jsoup.svg?branch=master)](https://travis-ci.org/jhy/jsoup) ## Example -Fetch the [Wikipedia](http://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html): +Fetch the [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html): ```java -Document doc = Jsoup.connect("http://en.wikipedia.org/").get(); +Document doc = Jsoup.connect("https://en.wikipedia.org/").get(); log(doc.title()); Elements newsHeadlines = doc.select("#mp-itn b a"); for (Element headline : newsHeadlines) { From a0711e57666055161a9eab9346e16e9ce2e0d547 Mon Sep 17 00:00:00 2001 From: Pascal Schumacher Date: Thu, 6 Feb 2020 23:18:51 +0100 Subject: [PATCH 377/774] Use String#replace instead of String#replaceAll where possible. String#replace does not use a regular expression for the replacement on Java 9+ and is therefore faster. --- src/main/java/org/jsoup/helper/HttpConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 50feccc519..10452b9adf 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -100,7 +100,7 @@ static URL encodeUrl(URL u) { try { // odd way to encode urls, but it works! String urlS = u.toExternalForm(); // URL external form may have spaces which is illegal in new URL() (odd asymmetry) - urlS = urlS.replaceAll(" ", "%20"); + urlS = urlS.replace(" ", "%20"); final URI uri = new URI(urlS); return new URL(uri.toASCIIString()); } catch (URISyntaxException | MalformedURLException e) { @@ -112,7 +112,7 @@ static URL encodeUrl(URL u) { private static String encodeMimeName(String val) { if (val == null) return null; - return val.replaceAll("\"", "%22"); + return val.replace("\"", "%22"); } private Connection.Request req; From f91e1e8dac668024c693a2ecd238b1c6b7219c30 Mon Sep 17 00:00:00 2001 From: Pascal Schumacher Date: Thu, 6 Feb 2020 23:09:02 +0100 Subject: [PATCH 378/774] Avoid unnecessary boxing of char in TokenQueue#chompBalanced. --- src/main/java/org/jsoup/parser/TokenQueue.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 854caf28cf..8b4cbb2e42 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -267,21 +267,21 @@ public String chompBalanced(char open, char close) { do { if (isEmpty()) break; - Character c = consume(); + char c = consume(); if (last == 0 || last != ESC) { - if (c.equals('\'') && c != open && !inDoubleQuote) + if (c == '\'' && c != open && !inDoubleQuote) inSingleQuote = !inSingleQuote; - else if (c.equals('"') && c != open && !inSingleQuote) + else if (c == '"' && c != open && !inSingleQuote) inDoubleQuote = !inDoubleQuote; if (inSingleQuote || inDoubleQuote) continue; - if (c.equals(open)) { + if (c == open) { depth++; if (start == -1) start = pos; } - else if (c.equals(close)) + else if (c == close) depth--; } From d06d8f423439290032dbbb8211c58220fa13e7bd Mon Sep 17 00:00:00 2001 From: Pascal Schumacher Date: Thu, 6 Feb 2020 23:01:01 +0100 Subject: [PATCH 379/774] Avoid unnecessary null checks, as Element#indexInList never returns null. --- src/main/java/org/jsoup/nodes/Element.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index d1cde383fc..7c1f8c2ca4 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -706,8 +706,7 @@ public Elements siblingElements() { public Element nextElementSibling() { if (parentNode == null) return null; List siblings = parent().childElementsList(); - Integer index = indexInList(this, siblings); - Validate.notNull(index); + int index = indexInList(this, siblings); if (siblings.size() > index+1) return siblings.get(index+1); else @@ -731,8 +730,7 @@ public Elements nextElementSiblings() { public Element previousElementSibling() { if (parentNode == null) return null; List siblings = parent().childElementsList(); - Integer index = indexInList(this, siblings); - Validate.notNull(index); + int index = indexInList(this, siblings); if (index > 0) return siblings.get(index-1); else From e81c35f9ffa3eb56a7cdff92ff2e5d44f5e7ac51 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sat, 8 Feb 2020 18:03:58 -0800 Subject: [PATCH 380/774] Fix file to LF endings Unsure how this flipped to CRLF, and .gitattributes should be making everything LF. --- .../org/jsoup/parser/TokeniserStateTest.java | 504 +++++++++--------- 1 file changed, 252 insertions(+), 252 deletions(-) diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index 2479c60e87..0f5ea335d3 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -1,252 +1,252 @@ -package org.jsoup.parser; - -import org.jsoup.Jsoup; -import org.jsoup.TextUtil; -import org.jsoup.nodes.Comment; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.TextNode; -import org.jsoup.select.Elements; -import org.junit.Test; - -import java.util.Arrays; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -public class TokeniserStateTest { - - final char[] whiteSpace = { '\t', '\n', '\r', '\f', ' ' }; - final char[] quote = { '\'', '"' }; - - @Test - public void ensureSearchArraysAreSorted() { - char[][] arrays = { - TokeniserState.attributeSingleValueCharsSorted, - TokeniserState.attributeDoubleValueCharsSorted, - TokeniserState.attributeNameCharsSorted, - TokeniserState.attributeValueUnquoted - }; - - for (char[] array : arrays) { - char[] copy = Arrays.copyOf(array, array.length); - Arrays.sort(array); - assertArrayEquals(array, copy); - } - } - - @Test - public void testCharacterReferenceInRcdata() { - String body = ""; - Document doc = Jsoup.parse(body); - Elements els = doc.select("textarea"); - assertEquals("You&I", els.text()); - } - - @Test - public void testBeforeTagName() { - for (char c : whiteSpace) { - String body = String.format("test
    ", c); - Document doc = Jsoup.parse(body); - Elements els = doc.select("div"); - assertEquals("test", els.text()); - } - } - - @Test - public void testEndTagOpen() { - String body; - Document doc; - Elements els; - - body = "
    hello worldhello world
    "; - doc = Jsoup.parse(body); - els = doc.select("div"); - assertEquals("hello world", els.text()); - - body = "
    fake
    "; - doc = Jsoup.parse(body); - els = doc.select("div"); - assertEquals("fake", els.text()); - - body = "
    fake"; - doc = Jsoup.parse(body); - els = doc.select("div"); - assertEquals("fake", els.text()); - } - - @Test - public void testRcdataLessthanSign() { - String body; - Document doc; - Elements els; - - body = ""; - doc = Jsoup.parse(body); - els = doc.select("textarea"); - assertEquals("", els.text()); - - body = ""; - doc = Jsoup.parse(body); - els = doc.select("textarea"); - assertEquals("hello worlddata", c); - Document doc = Jsoup.parse(body); - Elements els = doc.select("textarea"); - assertEquals("data", els.text()); - } - } - - @Test - public void testCommentEndCoverage() { - String html = "

    Hello

    "; - Document doc = Jsoup.parse(html); - - Element body = doc.body(); - Comment comment = (Comment) body.childNode(1); - assertEquals("
    --! --- ", comment.getData()); - Element p = body.child(1); - TextNode text = (TextNode) p.childNode(0); - assertEquals("Hello", text.getWholeText()); - } - - @Test - public void testCommentEndBangCoverage() { - String html = " -->

    Hello

    "; - Document doc = Jsoup.parse(html); - - Element body = doc.body(); - Comment comment = (Comment) body.childNode(1); - assertEquals("
    --!-", comment.getData()); - Element p = body.child(1); - TextNode text = (TextNode) p.childNode(0); - assertEquals("Hello", text.getWholeText()); - } - - @Test - public void testPublicIdentifiersWithWhitespace() { - String expectedOutput = ""; - for (char q : quote) { - for (char ws : whiteSpace) { - String[] htmls = { - String.format("", ws, q, q), - String.format("", ws, q, q), - String.format("", ws, q, q), - String.format("", ws, q, q), - String.format("", q, q, ws), - String.format("", q, q, ws) - }; - for (String html : htmls) { - Document doc = Jsoup.parse(html); - assertEquals(expectedOutput, doc.childNode(0).outerHtml()); - } - } - } - } - - @Test - public void testSystemIdentifiersWithWhitespace() { - String expectedOutput = ""; - for (char q : quote) { - for (char ws : whiteSpace) { - String[] htmls = { - String.format("", ws, q, q), - String.format("", ws, q, q), - String.format("", ws, q, q), - String.format("", ws, q, q), - String.format("", q, q, ws), - String.format("", q, q, ws) - }; - for (String html : htmls) { - Document doc = Jsoup.parse(html); - assertEquals(expectedOutput, doc.childNode(0).outerHtml()); - } - } - } - } - - @Test - public void testPublicAndSystemIdentifiersWithWhitespace() { - String expectedOutput = ""; - for (char q : quote) { - for (char ws : whiteSpace) { - String[] htmls = { - String.format("", q, q, ws, q, q), - String.format("", q, q, q, q) - }; - for (String html : htmls) { - Document doc = Jsoup.parse(html); - assertEquals(expectedOutput, doc.childNode(0).outerHtml()); - } - } - } - } - - @Test public void handlesLessInTagThanAsNewTag() { - // out of spec, but clear author intent - String html = "Two"; - Document doc = Jsoup.parse(html); - assertEquals("

    Two
    ", TextUtil.stripNewlines(doc.body().html())); - } - - @Test - public void testUnconsumeAtBufferBoundary() { - String triggeringSnippet = "test
    ", c); + Document doc = Jsoup.parse(body); + Elements els = doc.select("div"); + assertEquals("test", els.text()); + } + } + + @Test + public void testEndTagOpen() { + String body; + Document doc; + Elements els; + + body = "
    hello worldhello world
    "; + doc = Jsoup.parse(body); + els = doc.select("div"); + assertEquals("hello world", els.text()); + + body = "
    fake
    "; + doc = Jsoup.parse(body); + els = doc.select("div"); + assertEquals("fake", els.text()); + + body = "
    fake"; + doc = Jsoup.parse(body); + els = doc.select("div"); + assertEquals("fake", els.text()); + } + + @Test + public void testRcdataLessthanSign() { + String body; + Document doc; + Elements els; + + body = ""; + doc = Jsoup.parse(body); + els = doc.select("textarea"); + assertEquals("", els.text()); + + body = ""; + doc = Jsoup.parse(body); + els = doc.select("textarea"); + assertEquals("hello worlddata", c); + Document doc = Jsoup.parse(body); + Elements els = doc.select("textarea"); + assertEquals("data", els.text()); + } + } + + @Test + public void testCommentEndCoverage() { + String html = "

    Hello

    "; + Document doc = Jsoup.parse(html); + + Element body = doc.body(); + Comment comment = (Comment) body.childNode(1); + assertEquals("
    --! --- ", comment.getData()); + Element p = body.child(1); + TextNode text = (TextNode) p.childNode(0); + assertEquals("Hello", text.getWholeText()); + } + + @Test + public void testCommentEndBangCoverage() { + String html = " -->

    Hello

    "; + Document doc = Jsoup.parse(html); + + Element body = doc.body(); + Comment comment = (Comment) body.childNode(1); + assertEquals("
    --!-", comment.getData()); + Element p = body.child(1); + TextNode text = (TextNode) p.childNode(0); + assertEquals("Hello", text.getWholeText()); + } + + @Test + public void testPublicIdentifiersWithWhitespace() { + String expectedOutput = ""; + for (char q : quote) { + for (char ws : whiteSpace) { + String[] htmls = { + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", q, q, ws), + String.format("", q, q, ws) + }; + for (String html : htmls) { + Document doc = Jsoup.parse(html); + assertEquals(expectedOutput, doc.childNode(0).outerHtml()); + } + } + } + } + + @Test + public void testSystemIdentifiersWithWhitespace() { + String expectedOutput = ""; + for (char q : quote) { + for (char ws : whiteSpace) { + String[] htmls = { + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", ws, q, q), + String.format("", q, q, ws), + String.format("", q, q, ws) + }; + for (String html : htmls) { + Document doc = Jsoup.parse(html); + assertEquals(expectedOutput, doc.childNode(0).outerHtml()); + } + } + } + } + + @Test + public void testPublicAndSystemIdentifiersWithWhitespace() { + String expectedOutput = ""; + for (char q : quote) { + for (char ws : whiteSpace) { + String[] htmls = { + String.format("", q, q, ws, q, q), + String.format("", q, q, q, q) + }; + for (String html : htmls) { + Document doc = Jsoup.parse(html); + assertEquals(expectedOutput, doc.childNode(0).outerHtml()); + } + } + } + } + + @Test public void handlesLessInTagThanAsNewTag() { + // out of spec, but clear author intent + String html = "Two"; + Document doc = Jsoup.parse(html); + assertEquals("

    Two
    ", TextUtil.stripNewlines(doc.body().html())); + } + + @Test + public void testUnconsumeAtBufferBoundary() { + String triggeringSnippet = "
    Date: Sat, 8 Feb 2020 18:09:41 -0800 Subject: [PATCH 381/774] [maven-release-plugin] prepare release jsoup-1.12.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7e6803abf2..66db3e4679 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.12.2-SNAPSHOT + 1.12.2 jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - HEAD + jsoup-1.12.2 Jonathan Hedley From 437c727f6c33272c7a971aa75cbfd38d666fc229 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 9 Feb 2020 05:27:11 -0800 Subject: [PATCH 382/774] Prep 1.13.1 development --- CHANGES | 5 ++++- pom.xml | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index aa6fe21ced..ecb7001175 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog -**** Release 1.12.2 [PENDING] +*** + * Release 1.13.1 [PENDING] + +**** Release 1.12.2 [2020-Feb-08] * Improvement: the :has() selector now supports relative selectors. For example, the query "div:has(> a)" will select all "div" elements that have at least one direct child "a" element. diff --git a/pom.xml b/pom.xml index 66db3e4679..38ffa842c0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jsoup jsoup - 1.12.2 + 1.13.1-SNAPSHOT jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do. https://jsoup.org/ 2009 @@ -24,7 +24,7 @@ https://github.com/jhy/jsoup scm:git:https://github.com/jhy/jsoup.git - jsoup-1.12.2 + HEAD Jonathan Hedley From a7b0c2df028f49fa246c7b6a51b6b83011655865 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 9 Feb 2020 06:29:52 -0800 Subject: [PATCH 383/774] Removed old deprecated classes and methods --- CHANGES | 4 +-- src/main/java/org/jsoup/nodes/Attribute.java | 7 ----- src/main/java/org/jsoup/nodes/Attributes.java | 4 +-- .../org/jsoup/nodes/BooleanAttribute.java | 20 ------------- src/main/java/org/jsoup/nodes/Comment.java | 12 -------- src/main/java/org/jsoup/nodes/DataNode.java | 10 ------- .../java/org/jsoup/nodes/DocumentType.java | 29 ------------------- src/main/java/org/jsoup/nodes/Entities.java | 13 +-------- src/main/java/org/jsoup/nodes/TextNode.java | 25 ---------------- .../java/org/jsoup/nodes/XmlDeclaration.java | 12 -------- src/main/java/org/jsoup/parser/Parser.java | 11 ------- src/main/java/org/jsoup/parser/Tag.java | 20 ------------- src/main/java/org/jsoup/parser/Token.java | 1 - .../java/org/jsoup/select/NodeTraversor.java | 20 ------------- .../java/org/jsoup/nodes/AttributeTest.java | 2 ++ .../java/org/jsoup/nodes/ElementTest.java | 2 -- .../org/jsoup/parser/AttributeParseTest.java | 8 +---- 17 files changed, 7 insertions(+), 193 deletions(-) delete mode 100644 src/main/java/org/jsoup/nodes/BooleanAttribute.java diff --git a/CHANGES b/CHANGES index ecb7001175..440b23a879 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ jsoup changelog -*** - * Release 1.13.1 [PENDING] +*** Release 1.13.1 [PENDING] + * Removed old methods and classes that were marked deprecated in previous releases. **** Release 1.12.2 [2020-Feb-08] * Improvement: the :has() selector now supports relative selectors. For example, the query diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 8a51d1008b..ce89c42d45 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -168,13 +168,6 @@ protected static boolean shouldCollapseAttribute(final String key, final String (val == null || ("".equals(val) || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); } - /** - * @deprecated - */ - protected boolean isBooleanAttribute() { - return Arrays.binarySearch(booleanAttributes, key) >= 0 || val == null; - } - /** * Checks if this attribute name is defined as a boolean attribute in HTML5 */ diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 85dabcf261..c2fdce68ed 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -288,9 +288,7 @@ public void remove() { public List asList() { ArrayList list = new ArrayList<>(size); for (int i = 0; i < size; i++) { - Attribute attr = vals[i] == null ? - new BooleanAttribute(keys[i]) : // deprecated class, but maybe someone still wants it - new Attribute(keys[i], vals[i], Attributes.this); + Attribute attr = new Attribute(keys[i], vals[i], Attributes.this); list.add(attr); } return Collections.unmodifiableList(list); diff --git a/src/main/java/org/jsoup/nodes/BooleanAttribute.java b/src/main/java/org/jsoup/nodes/BooleanAttribute.java deleted file mode 100644 index 583ea4ff24..0000000000 --- a/src/main/java/org/jsoup/nodes/BooleanAttribute.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.jsoup.nodes; - -/** - * A boolean attribute that is written out without any value. - * @deprecated just use null values (vs empty string) for booleans. - */ -public class BooleanAttribute extends Attribute { - /** - * Create a new boolean attribute from unencoded (raw) key. - * @param key attribute key - */ - public BooleanAttribute(String key) { - super(key, null); - } - - @Override - protected boolean isBooleanAttribute() { - return true; - } -} diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index 5e141f2a8b..b41ab1939d 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -10,8 +10,6 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class Comment extends LeafNode { - private static final String COMMENT_KEY = "comment"; - /** Create a new comment node. @param data The contents of the comment @@ -20,16 +18,6 @@ public Comment(String data) { value = data; } - /** - Create a new comment node. - @param data The contents of the comment - @param baseUri base URI not used. This is a leaf node. - @deprecated - */ - public Comment(String data, String baseUri) { - this(data); - } - public String nodeName() { return "#comment"; } diff --git a/src/main/java/org/jsoup/nodes/DataNode.java b/src/main/java/org/jsoup/nodes/DataNode.java index 270d5ed114..336cc6a310 100644 --- a/src/main/java/org/jsoup/nodes/DataNode.java +++ b/src/main/java/org/jsoup/nodes/DataNode.java @@ -16,16 +16,6 @@ public DataNode(String data) { value = data; } - /** - Create a new DataNode. - @param data data contents - @param baseUri Unused, Leaf Nodes do not hold base URis - @deprecated use {@link #DataNode(String)} instead - */ - public DataNode(String data, String baseUri) { - this(data); - } - public String nodeName() { return "#data"; } diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index b94f2c76d2..6c181e522b 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -35,35 +35,6 @@ public DocumentType(String name, String publicId, String systemId) { updatePubSyskey(); } - /** - * Create a new doctype element. - * @param name the doctype's name - * @param publicId the doctype's public ID - * @param systemId the doctype's system ID - * @param baseUri unused - * @deprecated - */ - public DocumentType(String name, String publicId, String systemId, String baseUri) { - attr(NAME, name); - attr(PUBLIC_ID, publicId); - attr(SYSTEM_ID, systemId); - updatePubSyskey(); - } - - /** - * Create a new doctype element. - * @param name the doctype's name - * @param publicId the doctype's public ID - * @param systemId the doctype's system ID - * @param baseUri unused - * @deprecated - */ - public DocumentType(String name, String pubSysKey, String publicId, String systemId, String baseUri) { - attr(NAME, name); - attr(PUBLIC_ID, publicId); - attr(SYSTEM_ID, systemId); - updatePubSyskey(); - } public void setPubSysKey(String value) { if (value != null) attr(PUB_SYS_KEY, value); diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index d7ce2101e3..1e5345914f 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -97,17 +97,6 @@ public static boolean isBaseNamedEntity(final String name) { return base.codepointForName(name) != empty; } - /** - * Get the Character value of the named entity - * - * @param name named entity (e.g. "lt" or "amp") - * @return the Character value of the named entity (e.g. '{@literal <}' or '{@literal &}') - * @deprecated does not support characters outside the BMP or multiple character names - */ - public static Character getCharacterByName(String name) { - return (char) extended.codepointForName(name); - } - /** * Get the character(s) represented by the named entity * @@ -247,7 +236,7 @@ static void escape(Appendable accum, String string, Document.OutputSettings out, private static void appendEncoded(Appendable accum, EscapeMode escapeMode, int codePoint) throws IOException { final String name = escapeMode.nameForCodepoint(codePoint); - if (name != emptyName) // ok for identity check + if (!emptyName.equals(name)) // ok for identity check accum.append('&').append(name).append(';'); else accum.append("&#x").append(Integer.toHexString(codePoint)).append(';'); diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 26ee95e3c9..23ad9e7252 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -10,7 +10,6 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextNode extends LeafNode { - /** Create a new TextNode representing the supplied (unencoded) text). @@ -21,18 +20,6 @@ public TextNode(String text) { value = text; } - /** - Create a new TextNode representing the supplied (unencoded) text). - - @param text raw text - @param baseUri base uri - ignored for this node type - @see #createFromEncoded(String, String) - @deprecated use {@link TextNode#TextNode(String)} - */ - public TextNode(String text, String baseUri) { - this(text); - } - public String nodeName() { return "#text"; } @@ -113,18 +100,6 @@ public TextNode clone() { return (TextNode) super.clone(); } - /** - * Create a new TextNode from HTML encoded (aka escaped) data. - * @param encodedText Text containing encoded HTML (e.g. &lt;) - * @param baseUri Base uri - * @return TextNode containing unencoded data (e.g. <) - * @deprecated use {@link TextNode#createFromEncoded(String)} instead, as LeafNodes don't carry base URIs. - */ - public static TextNode createFromEncoded(String encodedText, String baseUri) { - String text = Entities.unescape(encodedText); - return new TextNode(text); - } - /** * Create a new TextNode from HTML encoded (aka escaped) data. * @param encodedText Text containing encoded HTML (e.g. &lt;) diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index 2805886f76..d7906eb6a6 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -24,18 +24,6 @@ public XmlDeclaration(String name, boolean isProcessingInstruction) { this.isProcessingInstruction = isProcessingInstruction; } - /** - * Create a new XML declaration - * @param name of declaration - * @param baseUri Leaf Nodes don't have base URIs; they inherit from their Element - * @param isProcessingInstruction is processing instruction - * @see XmlDeclaration#XmlDeclaration(String, boolean) - * @deprecated - */ - public XmlDeclaration(String name, String baseUri, boolean isProcessingInstruction) { - this(name, isProcessingInstruction); - } - public String nodeName() { return "#declaration"; } diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index be6d45ffa7..ae64918dc9 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -185,17 +185,6 @@ public static String unescapeEntities(String string, boolean inAttribute) { return tokeniser.unescapeEntities(inAttribute); } - /** - * @param bodyHtml HTML to parse - * @param baseUri baseUri base URI of document (i.e. original fetch location), for resolving relative URLs. - * - * @return parsed Document - * @deprecated Use {@link #parseBodyFragment} or {@link #parseFragment} instead. - */ - public static Document parseBodyFragmentRelaxed(String bodyHtml, String baseUri) { - return parse(bodyHtml, baseUri); - } - // builders /** diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 6257fc5b88..14270635a0 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -105,16 +105,6 @@ public boolean formatAsBlock() { return formatAsBlock; } - /** - * Gets if this tag can contain block tags. - * - * @return if tag can contain block tags - * @deprecated No longer used, and no different result than {{@link #isBlock()}} - */ - public boolean canContainBlock() { - return isBlock; - } - /** * Gets if this tag is an inline tag. * @@ -124,16 +114,6 @@ public boolean isInline() { return !isBlock; } - /** - * Gets if this tag is a data only tag. - * - * @return if this tag is a data only tag - * @deprecated use data nodes instead - */ - public boolean isData() { - return isBlock && !empty; - } - /** * Get if this is an empty tag * diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index ca26404c08..f8b86bcb1d 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -151,7 +151,6 @@ final boolean isSelfClosing() { return selfClosing; } - @SuppressWarnings({"TypeMayBeWeakened"}) final Attributes getAttributes() { return attributes; } diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index 9e0fe26734..9f3a567256 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -12,26 +12,6 @@ *

    */ public class NodeTraversor { - private NodeVisitor visitor; - - /** - * Create a new traversor. - * @param visitor a class implementing the {@link NodeVisitor} interface, to be called when visiting each node. - * @deprecated Just use the static {@link NodeTraversor#filter(NodeFilter, Node)} method. - */ - public NodeTraversor(NodeVisitor visitor) { - this.visitor = visitor; - } - - /** - * Start a depth-first traverse of the root and all of its descendants. - * @param root the root node point to traverse. - * @deprecated Just use the static {@link NodeTraversor#filter(NodeFilter, Node)} method. - */ - public void traverse(Node root) { - traverse(visitor, root); - } - /** * Start a depth-first traverse of the root and all of its descendants. * @param visitor Node visitor. diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 06b78c3bce..1eaa4ccaf8 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -4,6 +4,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class AttributeTest { @Test public void html() { @@ -36,6 +37,7 @@ public class AttributeTest { Attribute first = attributes.iterator().next(); assertEquals("hidden", first.getKey()); assertEquals("", first.getValue()); + assertTrue(Attribute.isBooleanAttribute(first.getKey())); } @Test public void settersOnOrphanAttribute() { diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index b20014941e..a4ff58f9dc 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -443,8 +443,6 @@ public class ElementTest { List attributes = div.attributes().asList(); assertEquals("There should be one attribute", 1, attributes.size()); - assertTrue("Attribute should be boolean", attributes.get(0) instanceof BooleanAttribute); - assertFalse(div.hasAttr("false")); assertEquals("
    ", div.outerHtml()); diff --git a/src/test/java/org/jsoup/parser/AttributeParseTest.java b/src/test/java/org/jsoup/parser/AttributeParseTest.java index c6db61837a..74b3002eb3 100644 --- a/src/test/java/org/jsoup/parser/AttributeParseTest.java +++ b/src/test/java/org/jsoup/parser/AttributeParseTest.java @@ -5,7 +5,6 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; -import org.jsoup.nodes.BooleanAttribute; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -83,12 +82,7 @@ public class AttributeParseTest { List attributes = el.attributes().asList(); assertEquals("There should be 3 attribute present", 3, attributes.size()); - // Assuming the list order always follows the parsed html - assertFalse("'normal' attribute should not be boolean", attributes.get(0) instanceof BooleanAttribute); - assertTrue("'boolean' attribute should be boolean", attributes.get(1) instanceof BooleanAttribute); - assertFalse("'empty' attribute should not be boolean", attributes.get(2) instanceof BooleanAttribute); - - assertEquals(html, el.outerHtml()); + assertEquals(html, el.outerHtml()); // vets boolean syntax } @Test public void dropsSlashFromAttributeName() { From 91fcf208945f458b176c2b6915b53bad095cf527 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 9 Feb 2020 06:50:00 -0800 Subject: [PATCH 384/774] Test for application/rss+xml --- .../java/org/jsoup/helper/HttpConnection.java | 2 +- .../org/jsoup/integration/ConnectTest.java | 25 ++++++++++++------- .../java/org/jsoup/integration/ParseTest.java | 12 ++++----- src/test/resources/htmltests/test-rss.xml | 20 +++++++++++++++ 4 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 src/test/resources/htmltests/test-rss.xml diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 10452b9adf..73cb7d4b36 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -768,7 +768,7 @@ else if (methodHasBody) && !contentType.startsWith("text/") && !xmlContentTypeRxp.matcher(contentType).matches() ) - throw new UnsupportedMimeTypeException("Unhandled content type. Must be text/*, application/xml, or application/xhtml+xml", + throw new UnsupportedMimeTypeException("Unhandled content type. Must be text/*, application/xml, or application/*+xml", contentType, req.url().toString()); // switch to the XML parser if content type is xml and not parser not explicitly set diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index cb4bf6dd09..a0193e22ab 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -3,35 +3,29 @@ import org.jsoup.Connection; import org.jsoup.HttpStatusException; import org.jsoup.Jsoup; -import org.jsoup.UncheckedIOException; import org.jsoup.integration.servlets.Deflateservlet; import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.FileServlet; import org.jsoup.integration.servlets.HelloServlet; import org.jsoup.integration.servlets.InterruptedServlet; import org.jsoup.integration.servlets.RedirectServlet; -import org.jsoup.integration.servlets.SlowRider; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; -import java.net.SocketTimeoutException; import java.net.URL; import java.util.Map; import static org.jsoup.helper.HttpConnection.CONTENT_TYPE; import static org.jsoup.helper.HttpConnection.MULTIPART_FORM_DATA; import static org.jsoup.integration.UrlConnectTest.browserUa; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Tests Jsoup.connect against a local server. @@ -416,7 +410,6 @@ public void handlesWrongContentLengthDuringBufferedRead() throws IOException { @Test public void getUtf8Bom() throws IOException { Connection con = Jsoup.connect(FileServlet.urlTo("/bomtests/bom_utf8.html")); - con.data(FileServlet.LocationParam, "/bomtests/bom_utf8.html"); Document doc = con.get(); assertEquals("UTF-8", con.response().charset()); @@ -434,11 +427,25 @@ public void testBinaryContentTypeThrowsException() { Document doc = con.response().parse(); } catch (IOException e) { threw = true; - assertEquals("Unhandled content type. Must be text/*, application/xml, or application/xhtml+xml", e.getMessage()); + assertEquals("Unhandled content type. Must be text/*, application/xml, or application/*+xml", e.getMessage()); } assertTrue(threw); } + @Test public void testParseRss() throws IOException { + // test that we switch automatically to xml, and we support application/rss+xml + Connection con = Jsoup.connect(FileServlet.urlTo("/htmltests/test-rss.xml")); + con.data(FileServlet.ContentTypeParam, "application/rss+xml"); + Document doc = con.get(); + Element title = doc.selectFirst("title"); + assertEquals("jsoup RSS news", title.text()); + assertEquals("channel", title.parent().nodeName()); + assertEquals("jsoup RSS news", doc.title()); + assertEquals(3, doc.select("link").size()); + assertEquals("application/rss+xml", con.response().contentType()); + assertEquals(Document.OutputSettings.Syntax.xml, doc.outputSettings().syntax()); + } + @Test public void canFetchBinaryAsBytes() throws IOException { Connection.Response res = Jsoup.connect(FileServlet.urlTo("/htmltests/thumb.jpg")) diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index 0bf45ba237..c59116e75d 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -8,6 +8,8 @@ import java.io.*; import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; import static org.junit.Assert.*; @@ -172,19 +174,15 @@ public void testLowercaseUtf8Charset() throws IOException { public static File getFile(String resourceName) { try { - File file = new File(ParseTest.class.getResource(resourceName).toURI()); - return file; + URL resource = ParseTest.class.getResource(resourceName); + return resource != null ? new File(resource.toURI()) : new File("/404"); } catch (URISyntaxException e) { throw new IllegalStateException(e); } } public static InputStream inputStreamFrom(String s) { - try { - return new ByteArrayInputStream(s.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + return new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)); } } diff --git a/src/test/resources/htmltests/test-rss.xml b/src/test/resources/htmltests/test-rss.xml new file mode 100644 index 0000000000..966ccafa22 --- /dev/null +++ b/src/test/resources/htmltests/test-rss.xml @@ -0,0 +1,20 @@ + + + + + jsoup RSS news + https://jsoup.org/ + jsoup HTML Parser News + + jsoup Java HTML Parser release 1.12.2 + https://jsoup.org/news/release-1.12.2 + jsoup 1.12.2 is out now, with a great set of improvements to connections, W3C interoperability, speed, and many bug fixes. + + + jsoup Java HTML Parser release 1.12.1 + https://jsoup.org/news/release-1.12.1 + jsoup 1.12.1 is out now, with a great set of improvements to connections, W3C interoperability, speed, and many bug fixes. + + + + From 50df99d6acacb32da25beb39ab4683edf5a48908 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Sun, 9 Feb 2020 07:47:46 -0800 Subject: [PATCH 385/774] Added some tests --- src/main/java/org/jsoup/nodes/Comment.java | 5 ++ .../java/org/jsoup/nodes/CommentTest.java | 60 +++++++++++++++++++ src/test/java/org/jsoup/parser/TagTest.java | 11 +++- .../org/jsoup/parser/TokeniserStateTest.java | 28 +++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/jsoup/nodes/CommentTest.java diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index b41ab1939d..25e77c9cca 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -30,6 +30,11 @@ public String getData() { return coreValue(); } + public Comment setData(String data) { + coreValue(data); + return this; + } + void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { if (out.prettyPrint()) indent(accum, depth, out); diff --git a/src/test/java/org/jsoup/nodes/CommentTest.java b/src/test/java/org/jsoup/nodes/CommentTest.java new file mode 100644 index 0000000000..8104f5803a --- /dev/null +++ b/src/test/java/org/jsoup/nodes/CommentTest.java @@ -0,0 +1,60 @@ +package org.jsoup.nodes; + +import org.jsoup.Jsoup; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class CommentTest { + private Comment comment = new Comment(" This is one heck of a comment! "); + private Comment decl = new Comment("?xml encoding='ISO-8859-1'?"); + + @Test + public void nodeName() { + assertEquals("#comment", comment.nodeName()); + } + + @Test + public void getData() { + assertEquals(" This is one heck of a comment! ", comment.getData()); + } + + @Test + public void testToString() { + // todo - want indent to check if inlining element and not add newline. + // also so

    OneTwo

    == "OneTwo" + assertEquals("\n", comment.toString()); + } + + @Test + public void testHtmlNoPretty() { + Document doc = Jsoup.parse(""); + doc.outputSettings().prettyPrint(false); + assertEquals(" ", doc.html()); + Node node = doc.childNode(0); + Comment c1 = (Comment) node; + assertEquals("", c1.outerHtml()); + } + + @Test + public void testClone() { + Comment c1 = comment.clone(); + assertNotSame(comment, c1); + assertEquals(comment.getData(), comment.getData()); + c1.setData("New"); + assertEquals("New", c1.getData()); + assertNotEquals(c1.getData(), comment.getData()); + } + + @Test + public void isXmlDeclaration() { + assertFalse(comment.isXmlDeclaration()); + assertTrue(decl.isXmlDeclaration()); + } + + @Test + public void asXmlDeclaration() { + XmlDeclaration xmlDeclaration = decl.asXmlDeclaration(); + assertNotNull(xmlDeclaration); + } +} diff --git a/src/test/java/org/jsoup/parser/TagTest.java b/src/test/java/org/jsoup/parser/TagTest.java index 507a08e045..d05751ac29 100644 --- a/src/test/java/org/jsoup/parser/TagTest.java +++ b/src/test/java/org/jsoup/parser/TagTest.java @@ -16,7 +16,7 @@ public class TagTest { @Test public void isCaseSensitive() { Tag p1 = Tag.valueOf("P"); Tag p2 = Tag.valueOf("p"); - assertFalse(p1.equals(p2)); + assertNotEquals(p1, p2); } @Test @MultiLocaleTest public void canBeInsensitive() { @@ -34,8 +34,8 @@ public class TagTest { @Test public void equality() { Tag p1 = Tag.valueOf("p"); Tag p2 = Tag.valueOf("p"); - assertTrue(p1.equals(p2)); - assertTrue(p1 == p2); + assertEquals(p1, p2); + assertSame(p1, p2); } @Test public void divSemantics() { @@ -75,4 +75,9 @@ public class TagTest { @Test(expected = IllegalArgumentException.class) public void valueOfChecksNotEmpty() { Tag.valueOf(" "); } + + @Test public void knownTags() { + assertTrue(Tag.isKnownTag("div")); + assertFalse(Tag.isKnownTag("explain")); + } } diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index 0f5ea335d3..f19391091e 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -249,4 +249,32 @@ public void testOpeningAngleBracketInTagName() { assertEquals(5, errorList.get(0).getPosition()); } + + @Test + public void rcData() { + Document doc = Jsoup.parse("One \0Two"); + assertEquals("One �Two", doc.title()); + } + + @Test + public void plaintext() { + Document doc = Jsoup.parse("
    One<div>Two</plaintext>\0no < Return"); + assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div>One<plaintext>&lt;div&gt;Two&lt;/plaintext&gt;�no &lt; Return</plaintext></div></body></html>", TextUtil.stripNewlines(doc.html())); + } + + @Test + public void nullInTag() { + Document doc = Jsoup.parse("<di\0v>One</di\0v>Two"); + assertEquals("<di�v>\n One\n</di�v>Two", doc.body().html()); + } + + @Test + public void attributeValUnquoted() { + Document doc = Jsoup.parse("<p name=foo&lt;bar>"); + Element p = doc.selectFirst("p"); + assertEquals("foo<bar", p.attr("name")); + + doc = Jsoup.parse("<p foo="); + assertEquals("<p foo></p>", doc.body().html()); + } } From a82d6b6ae8cb33cc7ccc78c7a805886a06b1dcd2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 08:07:08 -0800 Subject: [PATCH 386/774] Don't push inline comments to newline --- CHANGES | 2 ++ src/main/java/org/jsoup/nodes/Comment.java | 2 +- src/test/java/org/jsoup/nodes/CommentTest.java | 11 ++++++++--- src/test/java/org/jsoup/parser/HtmlParserTest.java | 2 +- .../java/org/jsoup/parser/XmlTreeBuilderTest.java | 4 ++-- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 440b23a879..6912ea4427 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ jsoup changelog *** Release 1.13.1 [PENDING] + * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline + * Removed old methods and classes that were marked deprecated in previous releases. **** Release 1.12.2 [2020-Feb-08] diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index 25e77c9cca..d80c500637 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -36,7 +36,7 @@ public Comment setData(String data) { } void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { - if (out.prettyPrint()) + if (out.prettyPrint() && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock()) || (out.outline() ))) indent(accum, depth, out); accum .append("<!--") diff --git a/src/test/java/org/jsoup/nodes/CommentTest.java b/src/test/java/org/jsoup/nodes/CommentTest.java index 8104f5803a..59d99ffc03 100644 --- a/src/test/java/org/jsoup/nodes/CommentTest.java +++ b/src/test/java/org/jsoup/nodes/CommentTest.java @@ -21,9 +21,14 @@ public void getData() { @Test public void testToString() { - // todo - want indent to check if inlining element and not add newline. - // also so <p>One<!-- comment -->Two</p> == "OneTwo" - assertEquals("\n<!-- This is one heck of a comment! -->", comment.toString()); + assertEquals("<!-- This is one heck of a comment! -->", comment.toString()); + + Document doc = Jsoup.parse("<div><!-- comment--></div>"); + assertEquals("<div>\n <!-- comment-->\n</div>", doc.body().html()); + + doc = Jsoup.parse("<p>One<!-- comment -->Two</p>"); + assertEquals("<p>One<!-- comment -->Two</p>", doc.body().html()); + assertEquals("OneTwo", doc.text()); } @Test diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 8cb827bfe7..00a26046b1 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1219,7 +1219,7 @@ public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/comments.html"); Document doc = Jsoup.parse(in, "UTF-8"); - assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--> <!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", + assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", StringUtil.normaliseWhitespace(doc.html())); assertEquals("A Certain Kind of Test", doc.head().select("title").text()); diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 683e16bcca..011e708ca9 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -109,7 +109,7 @@ public void testDoesNotForceSelfClosingKnownTags() { @Test public void handlesXmlDeclarationAsDeclaration() { String html = "<?xml encoding='UTF-8' ?><body>One</body><!-- comment -->"; Document doc = Jsoup.parse(html, "", Parser.xmlParser()); - assertEquals("<?xml encoding=\"UTF-8\"?> <body> One </body> <!-- comment -->", + assertEquals("<?xml encoding=\"UTF-8\"?> <body> One </body><!-- comment -->", StringUtil.normaliseWhitespace(doc.outerHtml())); assertEquals("#declaration", doc.childNode(0).nodeName()); assertEquals("#comment", doc.childNode(2).nodeName()); @@ -243,7 +243,7 @@ public void handlesLTinScript() { // https://github.com/jhy/jsoup/issues/1139 String html = "<script> var a=\"<?\"; var b=\"?>\"; </script>"; Document doc = Jsoup.parse(html, "", Parser.xmlParser()); - assertEquals("<script> var a=\"\n <!--?\"; var b=\"?-->\"; </script>", doc.html()); // converted from pseudo xmldecl to comment + assertEquals("<script> var a=\"<!--?\"; var b=\"?-->\"; </script>", doc.html()); // converted from pseudo xmldecl to comment } @Test public void dropsDuplicateAttributes() { From fbc7e76a2ac966a19b12c73147d3c6c8ab0eb61d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 08:23:18 -0800 Subject: [PATCH 387/774] Auto close optgroup in select Fixes #1313 --- CHANGES | 3 +++ .../jsoup/parser/HtmlTreeBuilderState.java | 4 ++-- .../java/org/jsoup/parser/HtmlParserTest.java | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 6912ea4427..cd089b4cf1 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,9 @@ jsoup changelog "div:has(> a)" will select all "div" elements that have at least one direct child "a" element. <https://github.com/jhy/jsoup/pull/1214> + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> + <https://github.com/jhy/jsoup/issues/1313> + * Improvement: added Element chaining methods for various overridden methods on Node. <https://github.com/jhy/jsoup/issues/1193> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 32b88d5db9..05c28ccd47 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1243,8 +1243,8 @@ else if (name.equals("option")) { tb.insert(start); } else if (name.equals("optgroup")) { if (tb.currentElement().normalName().equals("option")) - tb.processEndTag("option"); - else if (tb.currentElement().normalName().equals("optgroup")) + tb.processEndTag("option"); // pop option and flow to pop optgroup + if (tb.currentElement().normalName().equals("optgroup")) tb.processEndTag("optgroup"); tb.insert(start); } else if (name.equals("select")) { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 00a26046b1..b9d5c6194c 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1294,4 +1294,23 @@ public void testUNewlines() { Document doc = Jsoup.parse("<p>" + text); assertEquals(text, doc.text()); } + + @Test public void testStartOptGroup() { + // https://github.com/jhy/jsoup/issues/1313 + String html = "<select>\n" + + " <optgroup label=\"a\">\n" + + " <option>one\n" + + " <option>two\n" + + " <option>three\n" + + " <optgroup label=\"b\">\n" + + " <option>four\n" + + " <option>fix\n" + + " <option>six\n" + + "</select>"; + Document doc = Jsoup.parse(html); + Element select = doc.selectFirst("select"); + //assertEquals(2, select.childrenSize()); + + assertEquals("<optgroup label=\"a\"> <option>one </option><option>two </option><option>three </option></optgroup><optgroup label=\"b\"> <option>four </option><option>fix </option><option>six </option></optgroup>", select.html()); + } } From 08fa0064c33d0a076b425f53c550ee83e57d5c65 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 08:24:27 -0800 Subject: [PATCH 388/774] Assert harder --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index b9d5c6194c..8a145e43ed 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1309,7 +1309,7 @@ public void testUNewlines() { "</select>"; Document doc = Jsoup.parse(html); Element select = doc.selectFirst("select"); - //assertEquals(2, select.childrenSize()); + assertEquals(2, select.childrenSize()); assertEquals("<optgroup label=\"a\"> <option>one </option><option>two </option><option>three </option></optgroup><optgroup label=\"b\"> <option>four </option><option>fix </option><option>six </option></optgroup>", select.html()); } From 0a4bd07adf2f483207c0c0820f020a2361d841c3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 09:29:31 -0800 Subject: [PATCH 389/774] Don't create Attributes objects until required Saves memory & garbage --- CHANGES | 9 ++- src/main/java/org/jsoup/nodes/Element.java | 9 ++- .../org/jsoup/parser/HtmlTreeBuilder.java | 2 +- .../java/org/jsoup/parser/ParseSettings.java | 2 +- src/main/java/org/jsoup/parser/Token.java | 7 ++- .../java/org/jsoup/parser/XmlTreeBuilder.java | 3 +- .../java/org/jsoup/nodes/LeafNodeTest.java | 57 +++++++++++++++++++ 7 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/jsoup/nodes/LeafNodeTest.java diff --git a/CHANGES b/CHANGES index cd089b4cf1..e54dc7631e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,14 @@ jsoup changelog *** Release 1.13.1 [PENDING] + * Improvement: decent memory optimization by not creating Attribute objects for each Element unless/until the Element + gets an attribute. + * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> + <https://github.com/jhy/jsoup/issues/1313> + * Removed old methods and classes that were marked deprecated in previous releases. **** Release 1.12.2 [2020-Feb-08] @@ -10,9 +16,6 @@ jsoup changelog "div:has(> a)" will select all "div" elements that have at least one direct child "a" element. <https://github.com/jhy/jsoup/pull/1214> - * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> - <https://github.com/jhy/jsoup/issues/1313> - * Improvement: added Element chaining methods for various overridden methods on Node. <https://github.com/jhy/jsoup/issues/1193> diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 7c1f8c2ca4..ec5522faa8 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -51,7 +51,7 @@ public class Element extends Node { * @param tag tag name */ public Element(String tag) { - this(Tag.valueOf(tag), "", new Attributes()); + this(Tag.valueOf(tag), "", null); } /** @@ -180,7 +180,7 @@ public boolean isBlock() { * @return The id attribute, if present, or an empty string if not. */ public String id() { - return attributes().getIgnoreCase("id"); + return hasAttributes() ? attributes.getIgnoreCase("id") :""; } /** @@ -1293,7 +1293,10 @@ public Element classNames(Set<String> classNames) { */ // performance sensitive public boolean hasClass(String className) { - final String classAttr = attributes().getIgnoreCase("class"); + if (!hasAttributes()) + return false; + + final String classAttr = attributes.getIgnoreCase("class"); final int len = classAttr.length(); final int wantLen = className.length(); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 099859d18d..e8d480c543 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -196,7 +196,7 @@ void error(HtmlTreeBuilderState state) { Element insert(final Token.StartTag startTag) { // cleanup duplicate attributes: - if (!startTag.attributes.isEmpty()) { + if (startTag.attributes != null && !startTag.attributes.isEmpty()) { int dupes = startTag.attributes.deduplicate(settings); if (dupes > 0) { error("Duplicate attribute"); diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index 18c685a489..39485415f5 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -70,7 +70,7 @@ public String normalizeAttribute(String name) { } Attributes normalizeAttributes(Attributes attributes) { - if (!preserveAttributeCase) { + if (attributes != null && !preserveAttributeCase) { attributes.normalize(); } return attributes; diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index f8b86bcb1d..8ca79b42b6 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -152,6 +152,8 @@ final boolean isSelfClosing() { } final Attributes getAttributes() { + if (attributes == null) + attributes = new Attributes(); return attributes; } @@ -216,15 +218,14 @@ private void ensureAttributeValue() { final static class StartTag extends Tag { StartTag() { super(); - attributes = new Attributes(); + //attributes = new Attributes(); type = TokenType.StartTag; } @Override Tag reset() { super.reset(); - attributes = new Attributes(); - // todo - would prefer these to be null, but need to check Element assertions + attributes = null; return this; } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 7fbe251920..d331f405e5 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -75,7 +75,8 @@ private void insertNode(Node node) { Element insert(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name(), settings); // todo: wonder if for xml parsing, should treat all tags as unknown? because it's not html. - startTag.attributes.deduplicate(settings); + if (startTag.attributes != null) + startTag.attributes.deduplicate(settings); Element el = new Element(tag, baseUri, settings.normalizeAttributes(startTag.attributes)); insertNode(el); diff --git a/src/test/java/org/jsoup/nodes/LeafNodeTest.java b/src/test/java/org/jsoup/nodes/LeafNodeTest.java new file mode 100644 index 0000000000..0a2651af5d --- /dev/null +++ b/src/test/java/org/jsoup/nodes/LeafNodeTest.java @@ -0,0 +1,57 @@ +package org.jsoup.nodes; + +import org.jsoup.Jsoup; +import org.jsoup.select.Elements; +import org.jsoup.select.NodeFilter; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class LeafNodeTest { + + @Test + public void doesNotGetAttributesTooEasily() { + // test to make sure we're not setting attributes on all nodes right away + String body = "<p>One <!-- Two --> Three<![CDATA[Four]]></p>"; + Document doc = Jsoup.parse(body); + assertFalse(hasAnyAttributes(doc)); + + String s = doc.outerHtml(); + assertFalse(hasAnyAttributes(doc)); + + Elements els = doc.select("p"); + Element p = els.first(); + assertEquals(1, els.size()); + assertFalse(hasAnyAttributes(doc)); + + els = doc.select("p.none"); + assertFalse(hasAnyAttributes(doc)); + + String id = p.id(); + assertEquals("", id); + assertFalse(p.hasClass("Foobs")); + assertFalse(hasAnyAttributes(doc)); + } + + private boolean hasAnyAttributes(Node node) { + final boolean[] found = new boolean[1]; + node.filter(new NodeFilter() { + @Override + public FilterResult head(Node node, int depth) { + if (node.hasAttributes()) { + found[0] = true; + return FilterResult.STOP; + } else { + return FilterResult.CONTINUE; + } + } + + @Override + public FilterResult tail(Node node, int depth) { + return FilterResult.CONTINUE; + } + }); + return found[0]; + } +} From 52bbe52a0cc943708bb25f0a4c56421cfda2e1ae Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 10:43:35 -0800 Subject: [PATCH 390/774] Update test for varying XHTML impl formats --- src/test/java/org/jsoup/helper/W3CDomTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 91c54b95fc..45bb318e63 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -261,7 +261,8 @@ public void testRoundTripDoctype() { String publicDoc = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; assertEquals("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(publicDoc, true)); - assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head /><body /></html>", output(publicDoc, false)); + // different impls will have different XML formatting. OpenJDK 13 default gives this: <body /> but others have <body/>, so just check start + assertTrue(output(publicDoc, false).startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC")); String systemDoc = "<!DOCTYPE html SYSTEM \"exampledtdfile.dtd\">"; assertEquals("<!DOCTYPE html SYSTEM \"exampledtdfile.dtd\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(systemDoc, true)); From 1c51c84e68f0d76ee239a4a1ab52a487a4e2e764 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 10:48:50 -0800 Subject: [PATCH 391/774] Fix some jdoc warnings --- src/main/java/org/jsoup/internal/StringUtil.java | 1 - src/main/java/org/jsoup/nodes/Element.java | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 3c0e37a92b..bc1d5887ca 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -237,7 +237,6 @@ public static String resolve(final String baseUrl, final String relUrl) { * <p> * Care must be taken to release the builder once its work has been completed, with {@link #releaseBuilder} * @return an empty StringBuilder - * @ */ public static StringBuilder borrowBuilder() { synchronized (builders) { diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index ec5522faa8..b17054f54f 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -135,8 +135,9 @@ public String tagName() { /** * Get the normalized name of this Element's tag. This will always be the lowercased version of the tag, regardless - * of the tag case preserving setting of the parser. - * @return + * of the tag case preserving setting of the parser. For e.g., {@code <DIV>} and {@code <div>} both have a + * normal name of {@code div}. + * @return normal name */ public String normalName() { return tag.normalName(); From 2080cbb75a2746123eb89db2bd9f923e7c109e15 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 14:18:37 -0800 Subject: [PATCH 392/774] Added Attribute#hasValue, Attributes#hasValue(key) --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Attribute.java | 12 +++++++-- src/main/java/org/jsoup/nodes/Attributes.java | 25 +++++++++++++++++-- .../java/org/jsoup/nodes/AttributeTest.java | 14 +++++++++-- .../java/org/jsoup/nodes/AttributesTest.java | 16 ++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index e54dc7631e..d1c3a65f42 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,9 @@ jsoup changelog * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline + * Improvement: added Attributes#hasValue(key) and Attribute#hasValue(), to check if an attribute is set but has no + value. Useful in place of the deprecated and removed BooleanAttribute class and instanceof test. + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index ce89c42d45..c32fe22676 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -26,7 +26,7 @@ public class Attribute implements Map.Entry<String, String>, Cloneable { /** * Create a new attribute from unencoded (raw) key and value. * @param key attribute key; case is preserved. - * @param value attribute value + * @param value attribute value (may be null) * @see #createFromEncoded */ public Attribute(String key, String value) { @@ -73,13 +73,21 @@ public void setKey(String key) { } /** - Get the attribute value. + Get the attribute value. Will return an empty string if the value is not set. @return the attribute value */ public String getValue() { return Attributes.checkNotNull(val); } + /** + * Check if this Attribute has a value. Set boolean attributes have no value. + * @return if this is a boolean attribute / attribute without a value + */ + public boolean hasValue() { + return val != null; + } + /** Set the attribute value. @param val the new attribute value; must not be null diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index c2fdce68ed..4ce88e7796 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -126,11 +126,12 @@ public Attributes add(String key, String value) { /** * Set a new attribute, or replace an existing one by key. - * @param key case sensitive attribute key - * @param value attribute value + * @param key case sensitive attribute key (not null) + * @param value attribute value (may be null, to set a boolean attribute) * @return these attributes, for chaining */ public Attributes put(String key, String value) { + Validate.notNull(key); int i = indexOfKey(key); if (i != NotFound) vals[i] = value; @@ -227,6 +228,26 @@ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } + /** + * Check if these attributes contain an attribute with a value for this key. + * @param key key to check for + * @return true if key exists, and it has a value + */ + public boolean hasValue(String key) { + int i = indexOfKey(key); + return i != NotFound && vals[i] != null; + } + + /** + * Check if these attributes contain an attribute with a value for this key. + * @param key case-insensitive key to check for + * @return true if key exists, and it has a value + */ + public boolean hasValueIgnoreCase(String key) { + int i = indexOfKeyIgnoreCase(key); + return i != NotFound && vals[i] != null; + } + /** Get the number of attributes in this set. @return size diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 1eaa4ccaf8..54aab5fd58 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -3,8 +3,7 @@ import org.jsoup.Jsoup; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class AttributeTest { @Test public void html() { @@ -37,6 +36,7 @@ public class AttributeTest { Attribute first = attributes.iterator().next(); assertEquals("hidden", first.getKey()); assertEquals("", first.getValue()); + assertFalse(first.hasValue()); assertTrue(Attribute.isBooleanAttribute(first.getKey())); } @@ -49,4 +49,14 @@ public class AttributeTest { assertEquals("four", attr.getValue()); assertEquals(null, attr.parent); } + + @Test public void hasValue() { + Attribute a1 = new Attribute("one", ""); + Attribute a2 = new Attribute("two", null); + Attribute a3 = new Attribute("thr", "thr"); + + assertTrue(a1.hasValue()); + assertFalse(a2.hasValue()); + assertTrue(a3.hasValue()); + } } diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index b331636099..927d7d386f 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -155,4 +155,20 @@ public void testSetKeyConsistency() { assertFalse("Attribute 'a' not correctly removed", a.hasKey("a")); assertTrue("Attribute 'b' not present after renaming", a.hasKey("b")); } + + @Test + public void testBoolean() { + Attributes ats = new Attributes(); + ats.put("a", "a"); + ats.put("B", "b"); + ats.put("c", null); + + assertTrue(ats.hasValue("a")); + assertFalse(ats.hasValue("A")); + assertTrue(ats.hasValueIgnoreCase("A")); + + assertFalse(ats.hasValue("c")); + assertFalse(ats.hasValue("C")); + assertFalse(ats.hasValueIgnoreCase("C")); + } } From f93f2fe951a73a386cac980ad917a675ee557f67 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 9 Feb 2020 14:47:18 -0800 Subject: [PATCH 393/774] Speed tweaks in hot methods --- .../org/jsoup/parser/CharacterReader.java | 21 +++++++++++++++++++ .../java/org/jsoup/parser/TokeniserState.java | 14 ++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index f1587a9ae2..e34b97e401 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -290,6 +290,27 @@ String consumeData() { return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } + String consumeRawData() { + // <, null + //bufferUp(); // no need to bufferUp, just called consume() + int pos = bufPos; + final int start = pos; + final int remaining = bufLength; + final char[] val = charBuf; + + OUTER: while (pos < remaining) { + switch (val[pos]) { + case '<': + case TokeniserState.nullChar: + break OUTER; + default: + pos++; + } + } + bufPos = pos; + return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; + } + String consumeTagName() { // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar // NOTE: out of spec, added '<' to fix common author bugs diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index ab14f1d5da..266bf03202 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -55,7 +55,7 @@ void read(Tokeniser t, CharacterReader r) { t.emit(new Token.EOF()); break; default: - String data = r.consumeToAny('&', '<', nullChar); + String data = r.consumeData(); t.emit(data); break; } @@ -68,12 +68,12 @@ void read(Tokeniser t, CharacterReader r) { }, Rawtext { void read(Tokeniser t, CharacterReader r) { - readData(t, r, this, RawtextLessthanSign); + readRawData(t, r, this, RawtextLessthanSign); } }, ScriptData { void read(Tokeniser t, CharacterReader r) { - readData(t, r, this, ScriptDataLessthanSign); + readRawData(t, r, this, ScriptDataLessthanSign); } }, PLAINTEXT { @@ -744,7 +744,7 @@ void read(Tokeniser t, CharacterReader r) { }, AttributeValue_doubleQuoted { void read(Tokeniser t, CharacterReader r) { - String value = r.consumeToAny(attributeDoubleValueCharsSorted); + String value = r.consumeToAnySorted(attributeDoubleValueCharsSorted); if (value.length() > 0) t.tagPending.appendAttributeValue(value); else @@ -777,7 +777,7 @@ void read(Tokeniser t, CharacterReader r) { }, AttributeValue_singleQuoted { void read(Tokeniser t, CharacterReader r) { - String value = r.consumeToAny(attributeSingleValueCharsSorted); + String value = r.consumeToAnySorted(attributeSingleValueCharsSorted); if (value.length() > 0) t.tagPending.appendAttributeValue(value); else @@ -1683,7 +1683,7 @@ private static void handleDataEndTag(Tokeniser t, CharacterReader r, TokeniserSt } } - private static void readData(Tokeniser t, CharacterReader r, TokeniserState current, TokeniserState advance) { + private static void readRawData(Tokeniser t, CharacterReader r, TokeniserState current, TokeniserState advance) { switch (r.current()) { case '<': t.advanceTransition(advance); @@ -1697,7 +1697,7 @@ private static void readData(Tokeniser t, CharacterReader r, TokeniserState curr t.emit(new Token.EOF()); break; default: - String data = r.consumeToAny('<', nullChar); // todo - why hunt for null here? Just consumeTo'<'? + String data = r.consumeRawData(); t.emit(data); break; } From fb79deab9ddac9ac494ab444ba0086b935c10e6e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 12 Feb 2020 21:12:37 -0800 Subject: [PATCH 394/774] Ensure that attributes are nulled on clear() --- src/main/java/org/jsoup/nodes/Element.java | 7 ++++++- src/main/java/org/jsoup/parser/Token.java | 1 - src/test/java/org/jsoup/nodes/LeafNodeTest.java | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index b17054f54f..07ec18f019 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1504,7 +1504,12 @@ protected Element doClone(Node parent) { // overrides of Node for call chaining @Override public Element clearAttributes() { - return (Element) super.clearAttributes(); + if (attributes != null) { + super.clearAttributes(); + attributes = null; + } + + return this; } @Override diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 8ca79b42b6..5a3a0cfa98 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -218,7 +218,6 @@ private void ensureAttributeValue() { final static class StartTag extends Tag { StartTag() { super(); - //attributes = new Attributes(); type = TokenType.StartTag; } diff --git a/src/test/java/org/jsoup/nodes/LeafNodeTest.java b/src/test/java/org/jsoup/nodes/LeafNodeTest.java index 0a2651af5d..91dead8152 100644 --- a/src/test/java/org/jsoup/nodes/LeafNodeTest.java +++ b/src/test/java/org/jsoup/nodes/LeafNodeTest.java @@ -5,8 +5,7 @@ import org.jsoup.select.NodeFilter; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.*; public class LeafNodeTest { @@ -32,6 +31,18 @@ public void doesNotGetAttributesTooEasily() { assertEquals("", id); assertFalse(p.hasClass("Foobs")); assertFalse(hasAnyAttributes(doc)); + + p.addClass("Foobs"); + assertTrue(p.hasClass("Foobs")); + assertTrue(hasAnyAttributes(doc)); + assertTrue(hasAnyAttributes(p)); + + Attributes attributes = p.attributes(); + assertTrue(attributes.hasKey("class")); + p.clearAttributes(); + assertFalse(hasAnyAttributes(p)); + assertFalse(hasAnyAttributes(doc)); + assertFalse(attributes.hasKey("class")); } private boolean hasAnyAttributes(Node node) { From 6f2fd07c9a0035e796e7b549598f13563a1a7d7d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 12 Feb 2020 21:24:43 -0800 Subject: [PATCH 395/774] Renamed hasKey to hasKeyWithDefinedValue For clarity -- it's checking the value of the specified key -- it's not scanning for an attribute with that value. --- CHANGES | 5 +++-- src/main/java/org/jsoup/nodes/Attributes.java | 4 ++-- src/test/java/org/jsoup/nodes/AttributesTest.java | 12 ++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index d1c3a65f42..fe36f342c7 100644 --- a/CHANGES +++ b/CHANGES @@ -6,8 +6,9 @@ jsoup changelog * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline - * Improvement: added Attributes#hasValue(key) and Attribute#hasValue(), to check if an attribute is set but has no - value. Useful in place of the deprecated and removed BooleanAttribute class and instanceof test. + * Improvement: added Attributes#hasKeyWithDefinedValue(key) and Attribute#hasKeyIgnoreCaseWithDefinedValue(), to check + if an attribute is set but has no value. Useful in place of the deprecated and removed BooleanAttribute class and + instanceof test. * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 4ce88e7796..3e49d39b0a 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -233,7 +233,7 @@ public boolean hasKeyIgnoreCase(String key) { * @param key key to check for * @return true if key exists, and it has a value */ - public boolean hasValue(String key) { + public boolean hasKeyWithDefinedValue(String key) { int i = indexOfKey(key); return i != NotFound && vals[i] != null; } @@ -243,7 +243,7 @@ public boolean hasValue(String key) { * @param key case-insensitive key to check for * @return true if key exists, and it has a value */ - public boolean hasValueIgnoreCase(String key) { + public boolean hasKeyIgnoreCaseWithDefinedValue(String key) { int i = indexOfKeyIgnoreCase(key); return i != NotFound && vals[i] != null; } diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 927d7d386f..0ecf3ccb81 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -163,12 +163,12 @@ public void testBoolean() { ats.put("B", "b"); ats.put("c", null); - assertTrue(ats.hasValue("a")); - assertFalse(ats.hasValue("A")); - assertTrue(ats.hasValueIgnoreCase("A")); + assertTrue(ats.hasKeyWithDefinedValue("a")); + assertFalse(ats.hasKeyWithDefinedValue("A")); + assertTrue(ats.hasKeyIgnoreCaseWithDefinedValue("A")); - assertFalse(ats.hasValue("c")); - assertFalse(ats.hasValue("C")); - assertFalse(ats.hasValueIgnoreCase("C")); + assertFalse(ats.hasKeyWithDefinedValue("c")); + assertFalse(ats.hasKeyWithDefinedValue("C")); + assertFalse(ats.hasKeyIgnoreCaseWithDefinedValue("C")); } } From 979d5dd744b7cd30319965743b8a62269ba7845b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 12 Feb 2020 23:17:07 -0800 Subject: [PATCH 396/774] Introduced internal attributes And moved baseUri to them when set, vs a field on every element regardless of differing value. #1321 --- src/main/java/org/jsoup/nodes/Attributes.java | 33 ++++++++++- src/main/java/org/jsoup/nodes/Element.java | 35 ++++++----- src/main/java/org/jsoup/nodes/Node.java | 12 +--- .../org/jsoup/parser/HtmlTreeBuilder.java | 8 +-- .../java/org/jsoup/parser/XmlTreeBuilder.java | 2 +- .../java/org/jsoup/nodes/AttributesTest.java | 58 +++++++++++++++++++ .../java/org/jsoup/nodes/LeafNodeTest.java | 17 +++--- .../org/jsoup/parser/AttributeParseTest.java | 2 + 8 files changed, 129 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 3e49d39b0a..420c60146c 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -33,6 +33,9 @@ */ public class Attributes implements Iterable<Attribute>, Cloneable { protected static final String dataPrefix = "data-"; + // Indicates a jsoup internal key. Can't be set via HTML. (It could be set via accessor, but not too worried about + // that. Suppressed from list, iter. + static final char InternalPrefix = '/'; private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting // manages the key/val arrays @@ -253,7 +256,12 @@ public boolean hasKeyIgnoreCaseWithDefinedValue(String key) { @return size */ public int size() { - return size; + int s = 0; + for (int i = 0; i < size; i++) { + if (!isInternalKey(keys[i])) + s++; + } + return s; } /** @@ -285,6 +293,13 @@ public Iterator<Attribute> iterator() { @Override public boolean hasNext() { + while (i < size) { + if (isInternalKey(keys[i])) // skip over internal keys + i++; + else + break; + } + return i < size; } @@ -304,11 +319,13 @@ public void remove() { /** Get the attributes as a List, for iteration. - @return an view of the attributes as an unmodifialbe List. + @return an view of the attributes as an unmodifiable List. */ public List<Attribute> asList() { ArrayList<Attribute> list = new ArrayList<>(size); for (int i = 0; i < size; i++) { + if (isInternalKey(keys[i])) + continue; // skip internal keys Attribute attr = new Attribute(keys[i], vals[i], Attributes.this); list.add(attr); } @@ -327,7 +344,6 @@ public Map<String, String> dataset() { /** Get the HTML representation of these attributes. @return HTML - @throws SerializationException if the HTML representation of the attributes cannot be constructed. */ public String html() { StringBuilder sb = StringUtil.borrowBuilder(); @@ -342,6 +358,9 @@ public String html() { final void html(final Appendable accum, final Document.OutputSettings out) throws IOException { final int sz = size; for (int i = 0; i < sz; i++) { + if (isInternalKey(keys[i])) + continue; + // inlined from Attribute.html() final String key = keys[i]; final String val = vals[i]; @@ -498,4 +517,12 @@ public void remove() { private static String dataKey(String key) { return dataPrefix + key; } + + static String internalKey(String key) { + return InternalPrefix + key; + } + + private boolean isInternalKey(String key) { + return key != null && key.length() > 1 && key.charAt(0) == InternalPrefix; + } } diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 07ec18f019..799433a90c 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -40,11 +40,11 @@ public class Element extends Node { private static final List<Node> EMPTY_NODES = Collections.emptyList(); private static final Pattern classSplit = Pattern.compile("\\s+"); + private static final String baseUriKey = Attributes.internalKey("baseUri"); private Tag tag; private WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children List<Node> childNodes; private Attributes attributes; - private String baseUri; /** * Create a new, standalone element. @@ -58,26 +58,25 @@ public Element(String tag) { * Create a new, standalone Element. (Standalone in that is has no parent.) * * @param tag tag of this element - * @param baseUri the base URI - * @param attributes initial attributes + * @param baseUri the base URI (optional, may be null to inherit from parent, or "" to clear parent's) + * @param attributes initial attributes (optional, may be null) * @see #appendChild(Node) * @see #appendElement(String) */ public Element(Tag tag, String baseUri, Attributes attributes) { Validate.notNull(tag); - Validate.notNull(baseUri); childNodes = EMPTY_NODES; - this.baseUri = baseUri; this.attributes = attributes; this.tag = tag; + if (baseUri != null) + this.setBaseUri(baseUri); } - + /** - * Create a new Element from a tag and a base URI. + * Create a new Element from a Tag and a base URI. * * @param tag element tag - * @param baseUri the base URI of this element. It is acceptable for the base URI to be an empty - * string, but not null. + * @param baseUri the base URI of this element. Optional, and will inherit from its parent, if any. * @see Tag#valueOf(String, ParseSettings) */ public Element(Tag tag, String baseUri) { @@ -105,12 +104,22 @@ public Attributes attributes() { @Override public String baseUri() { - return baseUri; + return searchUpForAttribute(this, baseUriKey); + } + + private static String searchUpForAttribute(final Element start, final String key) { + Element el = start; + while (el != null) { + if (el.hasAttributes() && el.attributes.hasKey(key)) + return el.attributes.get(key); + el = el.parent(); + } + return ""; } @Override protected void doSetBaseUri(String baseUri) { - this.baseUri = baseUri; + attributes().put(baseUriKey, baseUri); } @Override @@ -1487,16 +1496,16 @@ public Element clone() { @Override public Element shallowClone() { // simpler than implementing a clone version with no child copy - return new Element(tag, baseUri, attributes == null ? null : attributes.clone()); + return new Element(tag, baseUri(), attributes == null ? null : attributes.clone()); } @Override protected Element doClone(Node parent) { Element clone = (Element) super.doClone(parent); clone.attributes = attributes != null ? attributes.clone() : null; - clone.baseUri = baseUri; clone.childNodes = new NodeList(clone, childNodes.size()); clone.childNodes.addAll(childNodes); // the children then get iterated and cloned in Node.clone + clone.setBaseUri(baseUri()); return clone; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 29f3edcdc2..5829631358 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -133,7 +133,7 @@ public Node clearAttributes() { } /** - Get the base URI of this node. + Get the base URI that applies to this node. Empty string if not defined. Used to make relative links absolute to. @return base URI */ public abstract String baseUri(); @@ -150,15 +150,7 @@ public Node clearAttributes() { */ public void setBaseUri(final String baseUri) { Validate.notNull(baseUri); - - traverse(new NodeVisitor() { - public void head(Node node, int depth) { - node.doSetBaseUri(baseUri); - } - - public void tail(Node node, int depth) { - } - }); + doSetBaseUri(baseUri); } /** diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index e8d480c543..22cf0bcef7 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -213,13 +213,13 @@ Element insert(final Token.StartTag startTag) { return el; } - Element el = new Element(Tag.valueOf(startTag.name(), settings), baseUri, settings.normalizeAttributes(startTag.attributes)); + Element el = new Element(Tag.valueOf(startTag.name(), settings), null, settings.normalizeAttributes(startTag.attributes)); insert(el); return el; } Element insertStartTag(String startTagName) { - Element el = new Element(Tag.valueOf(startTagName, settings), baseUri); + Element el = new Element(Tag.valueOf(startTagName, settings), null); insert(el); return el; } @@ -231,7 +231,7 @@ void insert(Element el) { Element insertEmpty(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name(), settings); - Element el = new Element(tag, baseUri, startTag.attributes); + Element el = new Element(tag, null, startTag.attributes); insertNode(el); if (startTag.isSelfClosing()) { if (tag.isKnownTag()) { @@ -246,7 +246,7 @@ Element insertEmpty(Token.StartTag startTag) { FormElement insertForm(Token.StartTag startTag, boolean onStack) { Tag tag = Tag.valueOf(startTag.name(), settings); - FormElement el = new FormElement(tag, baseUri, startTag.attributes); + FormElement el = new FormElement(tag, null, startTag.attributes); setFormElement(el); insertNode(el); if (onStack) diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index d331f405e5..361725119f 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -78,7 +78,7 @@ Element insert(Token.StartTag startTag) { if (startTag.attributes != null) startTag.attributes.deduplicate(settings); - Element el = new Element(tag, baseUri, settings.normalizeAttributes(startTag.attributes)); + Element el = new Element(tag, null, settings.normalizeAttributes(startTag.attributes)); insertNode(el); if (startTag.isSelfClosing()) { if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above. diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 0ecf3ccb81..1d38b9ae90 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import java.util.Iterator; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -120,6 +121,52 @@ public void testIterator() { assertEquals(datas.length, i); } + @Test + public void testIteratorSkipsInternal() { + Attributes a = new Attributes(); + a.put("One", "One"); + a.put(Attributes.internalKey("baseUri"), "example.com"); + a.put("Two", "Two"); + a.put(Attributes.internalKey("another"), "example.com"); + + Iterator<Attribute> it = a.iterator(); + assertTrue(it.hasNext()); + assertEquals("One", it.next().getKey()); + assertTrue(it.hasNext()); + assertEquals("Two", it.next().getKey()); + assertFalse(it.hasNext()); + + int seen = 0; + for (Attribute attribute : a) { + seen++; + } + assertEquals(2, seen); + } + + @Test + public void testListSkipsInternal() { + Attributes a = new Attributes(); + a.put("One", "One"); + a.put(Attributes.internalKey("baseUri"), "example.com"); + a.put("Two", "Two"); + a.put(Attributes.internalKey("another"), "example.com"); + + List<Attribute> attributes = a.asList(); + assertEquals(2, attributes.size()); + assertEquals("One", attributes.get(0).getKey()); + assertEquals("Two", attributes.get(1). getKey()); + } + + @Test public void htmlSkipsInternals() { + Attributes a = new Attributes(); + a.put("One", "One"); + a.put(Attributes.internalKey("baseUri"), "example.com"); + a.put("Two", "Two"); + a.put(Attributes.internalKey("another"), "example.com"); + + assertEquals(" One=\"One\" Two=\"Two\"", a.html()); + } + @Test public void testIteratorEmpty() { Attributes a = new Attributes(); @@ -171,4 +218,15 @@ public void testBoolean() { assertFalse(ats.hasKeyWithDefinedValue("C")); assertFalse(ats.hasKeyIgnoreCaseWithDefinedValue("C")); } + + @Test public void testSizeWhenHasInternal() { + Attributes a = new Attributes(); + a.put("One", "One"); + a.put("Two", "Two"); + assertEquals(2, a.size()); + + a.put(Attributes.internalKey("baseUri"), "example.com"); + a.put(Attributes.internalKey("another"), "example.com"); + assertEquals(2, a.size()); + } } diff --git a/src/test/java/org/jsoup/nodes/LeafNodeTest.java b/src/test/java/org/jsoup/nodes/LeafNodeTest.java index 91dead8152..5978fba676 100644 --- a/src/test/java/org/jsoup/nodes/LeafNodeTest.java +++ b/src/test/java/org/jsoup/nodes/LeafNodeTest.java @@ -14,34 +14,37 @@ public void doesNotGetAttributesTooEasily() { // test to make sure we're not setting attributes on all nodes right away String body = "<p>One <!-- Two --> Three<![CDATA[Four]]></p>"; Document doc = Jsoup.parse(body); - assertFalse(hasAnyAttributes(doc)); + assertTrue(hasAnyAttributes(doc)); // should have one - the base uri on the doc + + Element html = doc.child(0); + assertFalse(hasAnyAttributes(html)); String s = doc.outerHtml(); - assertFalse(hasAnyAttributes(doc)); + assertFalse(hasAnyAttributes(html)); Elements els = doc.select("p"); Element p = els.first(); assertEquals(1, els.size()); - assertFalse(hasAnyAttributes(doc)); + assertFalse(hasAnyAttributes(html)); els = doc.select("p.none"); - assertFalse(hasAnyAttributes(doc)); + assertFalse(hasAnyAttributes(html)); String id = p.id(); assertEquals("", id); assertFalse(p.hasClass("Foobs")); - assertFalse(hasAnyAttributes(doc)); + assertFalse(hasAnyAttributes(html)); p.addClass("Foobs"); assertTrue(p.hasClass("Foobs")); - assertTrue(hasAnyAttributes(doc)); + assertTrue(hasAnyAttributes(html)); assertTrue(hasAnyAttributes(p)); Attributes attributes = p.attributes(); assertTrue(attributes.hasKey("class")); p.clearAttributes(); assertFalse(hasAnyAttributes(p)); - assertFalse(hasAnyAttributes(doc)); + assertFalse(hasAnyAttributes(html)); assertFalse(attributes.hasKey("class")); } diff --git a/src/test/java/org/jsoup/parser/AttributeParseTest.java b/src/test/java/org/jsoup/parser/AttributeParseTest.java index 74b3002eb3..60bfab0a0d 100644 --- a/src/test/java/org/jsoup/parser/AttributeParseTest.java +++ b/src/test/java/org/jsoup/parser/AttributeParseTest.java @@ -51,6 +51,8 @@ public class AttributeParseTest { @Test public void canStartWithEq() { String html = "<a =empty />"; + // TODO this is the weirdest thing in the spec - why not consider this an attribute with an empty name, not where name is '='? + // am I reading it wrong? https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state Element el = Jsoup.parse(html).getElementsByTag("a").get(0); Attributes attr = el.attributes(); assertEquals(1, attr.size()); From 528ba552b19ab2ae949feecb373ef85a0b126566 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Thu, 13 Feb 2020 17:12:47 -0800 Subject: [PATCH 397/774] Updated to Attributes#hasDeclaredValueForKey --- CHANGES | 2 +- src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/main/java/org/jsoup/nodes/Attributes.java | 4 ++-- src/test/java/org/jsoup/nodes/AttributeTest.java | 8 ++++---- src/test/java/org/jsoup/nodes/AttributesTest.java | 12 ++++++------ 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index fe36f342c7..bac6cbafe2 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ jsoup changelog * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline - * Improvement: added Attributes#hasKeyWithDefinedValue(key) and Attribute#hasKeyIgnoreCaseWithDefinedValue(), to check + * Improvement: added Attributes#hasDeclaredValueForKey(key) and Attribute#hasDeclaredValueForKeyIgnoreCase(), to check if an attribute is set but has no value. Useful in place of the deprecated and removed BooleanAttribute class and instanceof test. diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index c32fe22676..ae74971fa1 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -84,7 +84,7 @@ public String getValue() { * Check if this Attribute has a value. Set boolean attributes have no value. * @return if this is a boolean attribute / attribute without a value */ - public boolean hasValue() { + public boolean hasDeclaredValue() { return val != null; } diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 420c60146c..3e9fc6b385 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -236,7 +236,7 @@ public boolean hasKeyIgnoreCase(String key) { * @param key key to check for * @return true if key exists, and it has a value */ - public boolean hasKeyWithDefinedValue(String key) { + public boolean hasDeclaredValueForKey(String key) { int i = indexOfKey(key); return i != NotFound && vals[i] != null; } @@ -246,7 +246,7 @@ public boolean hasKeyWithDefinedValue(String key) { * @param key case-insensitive key to check for * @return true if key exists, and it has a value */ - public boolean hasKeyIgnoreCaseWithDefinedValue(String key) { + public boolean hasDeclaredValueForKeyIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i != NotFound && vals[i] != null; } diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 54aab5fd58..2c064f6b95 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -36,7 +36,7 @@ public class AttributeTest { Attribute first = attributes.iterator().next(); assertEquals("hidden", first.getKey()); assertEquals("", first.getValue()); - assertFalse(first.hasValue()); + assertFalse(first.hasDeclaredValue()); assertTrue(Attribute.isBooleanAttribute(first.getKey())); } @@ -55,8 +55,8 @@ public class AttributeTest { Attribute a2 = new Attribute("two", null); Attribute a3 = new Attribute("thr", "thr"); - assertTrue(a1.hasValue()); - assertFalse(a2.hasValue()); - assertTrue(a3.hasValue()); + assertTrue(a1.hasDeclaredValue()); + assertFalse(a2.hasDeclaredValue()); + assertTrue(a3.hasDeclaredValue()); } } diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 1d38b9ae90..14872553df 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -210,13 +210,13 @@ public void testBoolean() { ats.put("B", "b"); ats.put("c", null); - assertTrue(ats.hasKeyWithDefinedValue("a")); - assertFalse(ats.hasKeyWithDefinedValue("A")); - assertTrue(ats.hasKeyIgnoreCaseWithDefinedValue("A")); + assertTrue(ats.hasDeclaredValueForKey("a")); + assertFalse(ats.hasDeclaredValueForKey("A")); + assertTrue(ats.hasDeclaredValueForKeyIgnoreCase("A")); - assertFalse(ats.hasKeyWithDefinedValue("c")); - assertFalse(ats.hasKeyWithDefinedValue("C")); - assertFalse(ats.hasKeyIgnoreCaseWithDefinedValue("C")); + assertFalse(ats.hasDeclaredValueForKey("c")); + assertFalse(ats.hasDeclaredValueForKey("C")); + assertFalse(ats.hasDeclaredValueForKeyIgnoreCase("C")); } @Test public void testSizeWhenHasInternal() { From 62c0595813b817ec593b0941ca4c3f29d10fac55 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 11:35:34 -0800 Subject: [PATCH 398/774] Preserve the mark when buffering So that we can rewind if required. Fixes #1324 --- CHANGES | 4 + .../org/jsoup/parser/CharacterReader.java | 19 +- src/main/java/org/jsoup/parser/Token.java | 2 +- .../java/org/jsoup/integration/ParseTest.java | 13 + .../org/jsoup/parser/CharacterReaderTest.java | 5 +- src/test/resources/htmltests/README | 1 + src/test/resources/htmltests/xwiki-1324.html | 1015 +++++++++++++++++ 7 files changed, 1048 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/htmltests/xwiki-1324.html diff --git a/CHANGES b/CHANGES index bac6cbafe2..36d32d6b90 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,10 @@ jsoup changelog * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> + * Bugfix: in CharacterReader when parsing an input stream, could throw a Mark Invalid exception if the reader was + marked, a bufferUp occurred, and then the reader was rewound. + <https://github.com/jhy/jsoup/issues/1324> + * Removed old methods and classes that were marked deprecated in previous releases. **** Release 1.12.2 [2020-Feb-08] diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index e34b97e401..54f7cfd45a 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -46,12 +46,18 @@ public CharacterReader(String input) { private boolean readFully; // if the underlying stream has been completely read, no value in further buffering private void bufferUp() { - if (readFully) + if (readFully || bufPos < bufSplitPoint) return; - final int pos = bufPos; - if (pos < bufSplitPoint) - return; + final int pos; + final int offset; + if (bufMark != -1) { + pos = bufMark; + offset = bufPos - bufMark; + } else { + pos = bufPos; + offset = 0; + } try { final long skipped = reader.skip(pos); @@ -70,8 +76,9 @@ private void bufferUp() { Validate.isTrue(skipped == pos); // Previously asserted that there is room in buf to skip, so this will be a WTF bufLength = read; readerPos += pos; - bufPos = 0; - bufMark = -1; + bufPos = offset; + if (bufMark != -1) + bufMark = 0; bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; } } catch (IOException e) { diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 5a3a0cfa98..4e28c30655 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -252,7 +252,7 @@ final static class EndTag extends Tag{ @Override public String toString() { - return "</" + name() + ">"; + return "</" + (tagName != null ? tagName : "(unset)") + ">"; } } diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index c59116e75d..e8050f3822 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -172,6 +172,19 @@ public void testLowercaseUtf8Charset() throws IOException { assertEquals("UTF-8", doc.outputSettings().charset().name()); } + @Test + public void testXwiki() throws IOException { + // https://github.com/jhy/jsoup/issues/1324 + File in = getFile("/htmltests/xwiki-1324.html"); + Document doc = Jsoup.parse(in, null, "https://localhost/"); + assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); + + // was getting busted at =userdirectory, because it hit the bufferup point but the mark was then lost. so + // updated to preserve the mark. + String wantHtml = "<a class=\"list-group-item\" data-id=\"userdirectory\" href=\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=userdirectory\" title=\"Customize the user directory live table.\">User Directory</a>"; + assertEquals(wantHtml, doc.select("[data-id=userdirectory]").outerHtml()); + } + public static File getFile(String resourceName) { try { URL resource = ParseTest.class.getResource(resourceName); diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 1ec8c59167..19a9530457 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -5,9 +5,7 @@ import java.io.BufferedReader; import java.io.StringReader; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Test suite for character reader. @@ -338,5 +336,4 @@ public void notEmptyAtBufferSplitPoint() { assertTrue(r.isEmpty()); } - } diff --git a/src/test/resources/htmltests/README b/src/test/resources/htmltests/README index 79881d47fb..eac7c92871 100644 --- a/src/test/resources/htmltests/README +++ b/src/test/resources/htmltests/README @@ -19,3 +19,4 @@ Sources * yahoo-jp.html http://www.yahoo.co.jp/index.html 12-Jan-2010 * baidu-cn-home.html http://www.baidu.com/ 15-Jul-2010 * nyt-article-1.html http://www.nytimes.com/2010/07/26/business/global/26bp.html?hp +* xwiki-1324.html https://github.com/jhy/jsoup/issues/1324 15-Feb-2020 diff --git a/src/test/resources/htmltests/xwiki-1324.html b/src/test/resources/htmltests/xwiki-1324.html new file mode 100644 index 0000000000..2dd7c33dc5 --- /dev/null +++ b/src/test/resources/htmltests/xwiki-1324.html @@ -0,0 +1,1015 @@ +<!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" data-xwiki-reference="xwiki:XWiki.XWikiPreferences" data-xwiki-document="XWiki.XWikiPreferences" data-xwiki-wiki="xwiki" data-xwiki-space="XWiki" data-xwiki-page="XWikiPreferences" data-xwiki-isnew="false" data-xwiki-version="3.1" data-xwiki-rest-url="/xwiki/rest/wikis/xwiki/spaces/XWiki/pages/XWikiPreferences" data-xwiki-form-token="epgA3yUHjVWOMi0Zxgi4eQ" data-xwiki-user-reference="xwiki:XWiki.Admin" data-xwiki-locale=""> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Global Administration - XWiki</title> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="shortcut icon" href="/xwiki/resources/icons/xwiki/favicon.ico?cache-version=1581617618000" /> + <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon16.png?cache-version=1581617618000" type="image/png" /> + <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon.svg?cache-version=1581617618000" type="image/svg+xml" /> + <link rel="apple-touch-icon" href="/xwiki/resources/icons/xwiki/favicon144.png?cache-version=1581617618000" /> + <link rel="canonical" href="/xwiki/bin/view/XWiki/XWikiPreferences" /> + <meta name="revisit-after" content="7 days" /> +<meta name="description" content="Global Administration" /> +<meta name="keywords" content="wiki" /> +<meta name="rating" content="General" /> +<meta name="author" content="superadmin" /> +<link rel="alternate" type="application/rss+xml" title="Wiki Feed RSS" href="/xwiki/bin/view/Main/WebRss?xpage=rdf" /> +<link rel="alternate" type="application/rss+xml" title="Blog RSS Feed" href="/xwiki/bin/view/Blog/GlobalBlogRss?xpage=plain" /> + <link href="/xwiki/webjars/wiki%3Axwiki/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.min.css" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/xwiki-platform-tree-webjar/12.1-SNAPSHOT/tree.min.css?evaluate=true" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/selectize.js/0.12.5/css/selectize.bootstrap3.css" type='text/css' rel='stylesheet'/> + <link href="/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/css/drawer.min.css" rel="stylesheet" type="text/css" /> + + + + + +<link href=" /xwiki/bin/skin/skins/flamingo/style.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg + " rel="stylesheet" type="text/css" media="all" /> +<link href=" /xwiki/bin/skin/skins/flamingo/print.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg + " rel="stylesheet" type="text/css" media="print" /> + <!--[if IE]> + <link href=" /xwiki/bin/skin/skins/flamingo/ie-all.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg + " rel="stylesheet" type="text/css" /> +<![endif]--> + + <link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/css/xwiki-min.css?cache-version=1581618408000&colorTheme=FlamingoThemes.Iceberg&amp;language=en'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/widgets/validation/livevalidation.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/suggest/xwiki.selectize.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/js/xwiki/table/livetable.css?cache-version=1581618404000'/> + <link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Style?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Tour/HomepageTour/WebHome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Settings?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminSheet?language=en&amp;docVersion=1.1&amp;colorTheme=FlamingoThemes.Iceberg" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Panels/Applications?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Help/SupportPanel/WebHome?language=en&amp;docVersion=1.1" /> + + <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/requirejs/2.3.6/require.min.js?r=1" + data-wysiwyg="true"></script> + + <script type='text/javascript' src='/xwiki/resources/js/prototype/prototype.js?cache-version=1581618402000'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/js/scriptaculous/effects.js?cache-version=1581618404000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/xwiki-min.js?cache-version=1581618408000&defer=false&amp;language=en'></script> +<script type='text/javascript' src='/xwiki/bin/skin/skins/flamingo/flamingo.min.js?cache-version=1581618398000&language=en' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.js?cache-version=1581617618000&h=1255116547' defer='defer'></script> +<script type='text/javascript' src='/xwiki/resources/uicomponents/lock/lock.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/resources/uicomponents/widgets/validation/livevalidation_prototype.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/async/async.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/hierarchy/hierarchy.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/widgets/tree.min.js?cache-version=1581618398000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/suggest/suggestUsersAndGroups.js?cache-version=1581618406000&language=en' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/table/livetable.js?cache-version=1581618404000' defer='defer'></script> + + <script type='text/javascript' src='/xwiki/bin/jsx/TourCode/TourJS?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Settings?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Script?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/QuickSearchUIX?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminSheet?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/Panels/Applications?language=en&amp;docVersion=1.1' defer='defer'></script> + + +<script type="text/javascript" src="/xwiki/resources/js/xwiki/compatibility.js?cache-version=1581618404000" defer="defer"></script> +<script type="text/javascript" src="/xwiki/resources/js/xwiki/markerScript.js?cache-version=1581618404000" defer="defer"></script> +<!--[if lt IE 9]> + <script src="/xwiki/webjars/wiki%3Axwiki/html5shiv/3.7.2/html5shiv.min.js?r=1"></script> + <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/respond/1.4.2/dest/respond.min.js?r=1" defer="defer"></script> +<![endif]--> +<script type="text/javascript" data-wysiwyg="true"> +// <![CDATA[ +require.config({ + paths: { + 'jquery': '/xwiki/webjars/wiki%3Axwiki/jquery/2.2.4/jquery.min.js?r=1', + 'bootstrap': '/xwiki/webjars/wiki%3Axwiki/bootstrap/3.4.1/js/bootstrap.min.js?r=1', + 'xwiki-meta': '/xwiki/resources/js/xwiki/meta.js?cache-version=1581618404000', + 'xwiki-events-bridge': "/xwiki/resources/js/xwiki/eventsBridge.js?cache-version=1581618404000", + 'iscroll': '/xwiki/webjars/wiki%3Axwiki/iscroll/5.1.3/build/iscroll-lite.js?r=1', + 'drawer': '/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/js/jquery.drawer.min.js?r=1', + 'deferred': "/xwiki/resources/uicomponents/require/deferred.js?cache-version=1581618406000" + }, + shim: { + 'bootstrap' : ['jquery'], + 'drawer': ['jquery', 'iscroll'] + }, + map: { + '*': { + 'jquery': 'jQueryNoConflict' + }, + 'jQueryNoConflict': { + 'jquery': 'jquery' + }, + } +}); +define('jQueryNoConflict', ['jquery'], function ($) { + return $.noConflict(); +}); +if (window.Prototype && Prototype.BrowserFeatures.ElementExtensions) { + require(['jquery', 'bootstrap'], function ($) { + // Fix incompatibilities between BootStrap and Prototype + var disablePrototypeJS = function (method, pluginsToDisable) { + var handler = function (event) { + event.target[method] = undefined; + setTimeout(function () { + delete event.target[method]; + }, 0); + }; + pluginsToDisable.each(function (plugin) { + $(window).on(method + '.bs.' + plugin, handler); + }); + }, + pluginsToDisable = ['collapse', 'dropdown', 'modal', 'tooltip', 'tab', 'popover']; + disablePrototypeJS('show', pluginsToDisable); + disablePrototypeJS('hide', pluginsToDisable); + }); +} +require(['jquery', 'drawer'], function($) { + $(document).ready(function() { + $('.drawer-main').closest('body').drawer(); + }); +}); +window.XWiki = window.XWiki || {}; +XWiki.webapppath = "xwiki/"; +XWiki.servletpath = "bin/"; +XWiki.contextPath = "/xwiki"; +XWiki.mainWiki = "xwiki"; +// Deprecated: replaced by meta data in the HTML element +XWiki.currentWiki = "xwiki"; +XWiki.currentSpace = "XWiki"; +XWiki.currentPage = "XWikiPreferences"; +XWiki.editor = "globaladmin"; +XWiki.viewer = ""; +XWiki.contextaction = "admin"; +XWiki.skin = 'XWiki.DefaultSkin'; +XWiki.docisnew = false; +XWiki.docsyntax = "xwiki/2.0"; +XWiki.docvariant = ""; +XWiki.blacklistedSpaces = [ ]; +XWiki.hasEdit = true; +XWiki.hasProgramming = true; +XWiki.hasBackupPackImportRights = true; +XWiki.hasRenderer = true; +window.docviewurl = "/xwiki/bin/view/XWiki/XWikiPreferences"; +window.docediturl = "/xwiki/bin/edit/XWiki/XWikiPreferences"; +window.docsaveurl = "/xwiki/bin/save/XWiki/XWikiPreferences"; +window.docgeturl = "/xwiki/bin/get/XWiki/XWikiPreferences"; +// ]]> +</script> + + <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> + <body id="body" class="skin-flamingo wiki-xwiki space-XWiki viewbody content panel-left-width-Medium panel-right-width-Medium drawer drawer-right drawer-close"> +<div id="xwikimaincontainer"> +<div id="xwikimaincontainerinner"> + + <div id="menuview"> + + + + + + + + + + + + + + + + + + + + + <nav class="navbar navbar-default actionmenu"> + <div class="container-fluid"> + <div class="navbar-header"> + <div id="companylogo"> + <a href="/xwiki/bin/view/Main/" title="Home" rel="home" class="navbar-brand"> + <img src="/xwiki/bin/download/FlamingoThemes/Iceberg/logo.svg?rev=1.1" alt="Wiki Logo"/> + </a> +</div> + + </div> + <div id="xwikimainmenu"> + + <ul class="nav navbar-nav navbar-left"> + <li class="divider" role="separator"></li> + </ul> + + <ul class="nav navbar-nav navbar-right"> + <li> + <a class="icon-navbar drawer-toggle" id="tmDrawerActivator" title="Drawer"><span class="sr-only">Toggle navigation</span><span class="fa fa-bars"></span></a> + </li> + <li class="navbar-avatar"> +<a href="/xwiki/bin/view/XWiki/Admin" class="icon-navbar"> +<span class="sr-only">User Profile</span> + <img class='avatar avatar_50' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/></a> +</li> + +<li class="dropdown" id="tmNotifications"> +<a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" +title="Notifications"> +<span class="sr-only"> +Toggle navigation +</span> +<span class="fa fa-bell"></span> +</a> +<ul class="dropdown-menu"> + <li> +<div class="notifications-header"> +<div class="clearfix"> +<div class="col-xs-4"> +<p><strong>Notifications</strong></p> +</div> +<div class="col-xs-8 text-right"> +<p> +<span class="notifications-header-link"> +<a href="/xwiki/bin/get/XWiki/Notifications/Code/NotificationRSSService?outputSyntax=plain" +class="notifications-header-link notifications-rss-link" rel="nofollow external"> +<span class="fa fa-rss"></span>&nbsp;RSS Feed +</a> +</span> +<span class="notifications-header-link"> +<a href="/xwiki/bin/view/XWiki/Admin?category=notifications" class="notifications-settings" rel="nofollow"> +<span class="fa fa-cog"></span>&nbsp;Settings +</a> +</span> +</p> +</div> +</div> +<div class="notifications-toggles clearfix" data-enabled="false"> +<pre> +<label class="hidden" for="notificationPageOnly">Toggle Page notifications only</label><input type="checkbox" id="notificationPageOnly" name="notificationPageOnly" checked="checked"/><label class="hidden" for="notificationPageAndChildren">Toggle Page and children notifications</label><input type="checkbox" id="notificationPageAndChildren" name="notificationPageAndChildren" checked="checked"/><label class="hidden" for="notificationWiki">Toggle Wiki notifications</label><input type="checkbox" id="notificationWiki" name="notificationWiki" checked="checked"/></pre> +</div> +<div class="notifications-header-uix col-xs-12"> +</div> +</div> +<div class="notifications-area loading clearfix"> +</div> +</li> + +</ul> +</li> + <li> +<form class="navbar-form globalsearch globalsearch-close form-inline" id="globalsearch" action="/xwiki/bin/view/Main/Search"> +<label class="hidden" for="headerglobalsearchinput">Search</label> +<input type="text" name="text" placeholder="search..." id="headerglobalsearchinput" /> +<button type="submit" class="btn" title="Search"><span class="fa fa-search"></span></button> +</form> +</li> + </ul> + + </div> </div> </nav> + + + + + + + +<div class="drawer-main drawer-default" id="tmDrawer"> + <nav class="drawer-nav"> + + <div class="drawer-brand clearfix"> + <a href="/xwiki/bin/view/XWiki/Admin"> + <img class='avatar avatar_120' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/> </a> + <div class="brand-links"> + <a href="/xwiki/bin/view/XWiki/Admin" class="brand-user" id="tmUser">Administrator</a> + <a href="/xwiki/bin/logout/XWiki/XWikiLogout?xredirect=%2Fxwiki%2Fbin%2Fadmin%2FXWiki%2FXWikiPreferences%3Feditor%3Dglobaladmin%26section%3DGroups" id="tmLogout" rel="nofollow"><span class="fa fa-sign-out"></span> Log-out</a> + </div> + </div> + + <ul class="drawer-menu"> + <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Home</li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/admin/XWiki/XWikiPreferences" id="tmAdminWiki" rel="nofollow"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-wrench"></span> + </div> + <div class="drawer-menu-item-text">Administer Wiki</div> + </a> + </li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/Main/AllDocs" id="tmWikiDocumentIndex"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-book"></span> + </div> + <div class="drawer-menu-item-text">Page Index</div> + </a> + </li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/Main/UserDirectory" id="tmMainUserIndex"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-user"></span> + </div> + <div class="drawer-menu-item-text">User Index</div> + </a> + </li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/Applications/" id="tmMainApplicationIndex"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-th"></span> + </div> + <div class="drawer-menu-item-text">Application Index</div> + </a> + </li> + <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Global</li> + + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/WikiManager/"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-list-alt"></span> + </div> + <div class="drawer-menu-item-text">Wiki Index</div> + </a> + </li> + + </ul> + </nav> +</div> + + + + + + </div> + <div id="headerglobal"> + <div id="globallinks"> + </div> <div class="clearfloats"></div> + + + </div> + + +<div class="content" id="contentcontainer"> +<div id="contentcontainerinner"> +<div class="leftsidecolumns"> + <div id="contentcolumn"> + <div class="main layoutsubsection"> +<div id="mainContentArea"> + + + + + + + + + + + + + + + + + + + + + + + + + <ol class="breadcrumb breadcrumb-expandable" data-entities="{xwiki:XWiki.XWikiPreferences=XWiki.XWikiPreferences}" data-entity="XWiki.XWikiPreferences" data-id="hierarchy" data-limit="5" data-treenavigation="true" id="hierarchy"><li class="wiki dropdown"><a href="/xwiki/bin/view/Main/"><span class="fa fa-home"></span></a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10"></div> +</div></li><li class="space dropdown"><a href="/xwiki/bin/view/XWiki/">XWiki</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.WebHome" data-responsive="true" data-root="{&quot;type&quot;:&quot;wiki&quot;,&quot;id&quot;:&quot;xwiki&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=wiki%3Axwiki"></div> +</div></li><li class="document active dropdown"><a href="/xwiki/bin/view/XWiki/XWikiPreferences">Global Administration</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{&quot;type&quot;:&quot;document&quot;,&quot;id&quot;:&quot;xwiki:XWiki.WebHome&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=document%3Axwiki%3AXWiki.WebHome"></div> +</div> </li></ol> + <div id="document-title"><h1 id="HGlobalAdministration:Groups" class="wikigeneratedid wikigeneratedheader"><span>Global Administration: Groups</span></h1><div class="noitems"><p>Manage user groups: add or remove groups, or change group members.</p></div><hr/></div><div id="administration-menu" class="panel-group admin-menu" role="tablist" aria-multiselectable="true"> +<div class="panel xform"> +<label for="adminsearchmenu" class="hidden">Search</label> +<input type="text" class="form-control panel-group-filter" autocomplete="off" id="adminsearchmenu" +placeholder="Search for..." /> +</div> + <div class="panel panel-default"> +<a class="panel-heading" role="tab" id="panel-heading-usersgroups" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=0" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-usersgroups" aria-expanded="true" aria-controls="panel-body-usersgroups" +title="Manage users, groups, and their access rights."><span class="fa fa-group"></span>Users &#38; Rights</a> +<div class="panel-collapse collapse in" role="tabpanel" id="panel-body-usersgroups" +aria-labelledby="panel-heading-usersgroups"> +<div class="list-group"> +<a class="list-group-item" data-id="Users" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Users" title="Manage users of this wiki: add, remove, modify their profile information." +>Users</a> +<a class="list-group-item active" data-id="Groups" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Groups" title="Manage user groups: add or remove groups, or change group members." +>Groups</a> +<a class="list-group-item" data-id="Rights" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Rights" title="Manage groups and users rights: control who can view, edit and delete pages." +>Rights</a> +<a class="list-group-item" data-id="UserProfile" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=UserProfile" title="Manage what information is displayed on the user profile of each user." +>User Profile</a> +<a class="list-group-item" data-id="userdirectory" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=userdirectory" title="Customize the user directory live table." +>User Directory</a> +<a class="list-group-item" data-id="Registration" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Registration" title="Manage user registration settings." +>Registration</a> +<a class="list-group-item" data-id="Invitation" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Invitation" title="Configure the Invitation Application" +>Invitation</a> +<a class="list-group-item" data-id="Authentication" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Authentication" title="" +>Authentication</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-extensionmanager" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=1" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-extensionmanager" aria-expanded="false" aria-controls="panel-body-extensionmanager" +title="Search, add, upgrade and remove extensions."><span class="fa fa-puzzle-piece"></span>Extensions</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-extensionmanager" +aria-labelledby="panel-heading-extensionmanager"> +<div class="list-group"> +<a class="list-group-item" data-id="XWiki.Extensions" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.Extensions" title="Search for new extensions to add to the wiki." +>Extensions</a> +<a class="list-group-item" data-id="XWiki.ExtensionHistory" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionHistory" title="See the history of the installed extensions." +>History</a> +<a class="list-group-item" data-id="XWiki.ExtensionUpdater" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionUpdater" title="Check if there are any updates available for the installed extensions." +>Updater</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-lf" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=2" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-lf" aria-expanded="false" aria-controls="panel-body-lf" +title="Change the aspect and layout of the wiki."><span class="fa fa-columns"></span>Look &#38; Feel</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-lf" +aria-labelledby="panel-heading-lf"> +<div class="list-group"> +<a class="list-group-item" data-id="Themes" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Themes" title="Customize the color and icon themes, skin and logo." +>Themes</a> +<a class="list-group-item" data-id="menu.name" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=menu.name" title="" +>Menus</a> +<a class="list-group-item" data-id="Panels.PanelWizard" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Panels.PanelWizard" title="Add and remove panels, change the page layout." +>Panels</a> +<a class="list-group-item" data-id="panels.applications" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.applications" title="Manage what applications are displayed in the Applications Panel." +>Applications Panel</a> +<a class="list-group-item" data-id="panels.navigation" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.navigation" title="Manage what pages are displayed in the Navigation Panel." +>Navigation Panel</a> +<a class="list-group-item" data-id="Presentation" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Presentation" title="Choose the page tabs that are visible and configure the page header and footer." +>Presentation</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-content" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=3" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-content" aria-expanded="false" aria-controls="panel-body-content" +title="Manipulate the content of the wiki."><span class="fa fa-file-text-o"></span>Content</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-content" +aria-labelledby="panel-heading-content"> +<div class="list-group"> +<a class="list-group-item" data-id="Templates" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Templates" title="Settings for the creation of page templates." +>Page Templates</a> +<a class="list-group-item" data-id="Localization" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Localization" title="Language-related settings." +>Localization</a> +<a class="list-group-item" data-id="Import" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Import" title="Import pages or applications into the wiki." +>Import</a> +<a class="list-group-item" data-id="Export" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Export" title="Export wiki pages into a XAR." +>Export</a> +<a class="list-group-item" data-id="Annotations" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Annotations" title="Configure the page annotations" +>Annotations</a> +<a class="list-group-item" data-id="XWiki.OfficeImporterAdmin" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.OfficeImporterAdmin" title="Configure the Office Server." +>Office Server</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-edit" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=4" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-edit" aria-expanded="false" aria-controls="panel-body-edit" +title="Configure the edit mode, the WYSIWYG editor and the available syntaxes."><span class="fa fa-pencil"></span>Editing</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-edit" +aria-labelledby="panel-heading-edit"> +<div class="list-group"> +<a class="list-group-item" data-id="Editing" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Editing" title="Choose the default edit mode and configure its title and versioning parameters." +>Edit Mode</a> +<a class="list-group-item" data-id="WYSIWYG" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=WYSIWYG" title="Choose the default WYSIWYG editor and configure it." +>WYSIWYG Editor</a> +<a class="list-group-item" data-id="Syntaxes" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Syntaxes" title="Choose the default page syntax and configure the available markup syntaxes." +>Syntaxes</a> +<a class="list-group-item" data-id="Name Strategies" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Name%20Strategies" title="" +>Name Strategies</a> +<a class="list-group-item" data-id="SyntaxHighlighting" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=SyntaxHighlighting" title="" +>Syntax Highlighting</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-email" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=5" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-email" aria-expanded="false" aria-controls="panel-body-email" +title="Configure mail sending parameters and view mail statuses."><span class="fa fa-envelope-o"></span>Mail</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-email" +aria-labelledby="panel-heading-email"> +<div class="list-group"> +<a class="list-group-item" data-id="emailSend" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailSend" title="Configure mail sending parameters." +>Mail Sending</a> +<a class="list-group-item" data-id="emailStatus" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailStatus" title="View the statuses of sent mails and resend mails that resulted in failure." +>Mail Sending Status</a> +<a class="list-group-item" data-id="emailGeneral" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailGeneral" title="Configure advanced mail parameters, like mail address obfuscation." +>Advanced</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-search" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=6" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-search" aria-expanded="false" aria-controls="panel-body-search" +title="Choose the default search engine or configure the search index."><span class="fa fa-search"></span>Search</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-search" +aria-labelledby="panel-heading-search"> +<div class="list-group"> +<a class="list-group-item" data-id="Search" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Search" title="Choose the default search engine or configure the search index." +>Search</a> +<a class="list-group-item" data-id="searchSuggest" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=searchSuggest" title="Configure the search suggest options." +>Search Suggest</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-wikis" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=7" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-wikis" aria-expanded="false" aria-controls="panel-body-wikis" +title="Wikis management."><span class="fa fa-globe"></span>Wikis</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-wikis" +aria-labelledby="panel-heading-wikis"> +<div class="list-group"> +<a class="list-group-item" data-id="wikis.descriptor" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.descriptor" title="Configure the wiki descriptor" +>Descriptor</a> +<a class="list-group-item" data-id="wikis.templates" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.templates" title="Manage the wiki templates" +>Wiki Templates</a> +<a class="list-group-item" data-id="wikis.rights" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.rights" title="" +>Creation Right</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-other" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=8" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-other" aria-expanded="false" aria-controls="panel-body-other" +title="Various configurations for extensions."><span class="fa fa-wrench"></span>Other</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-other" +aria-labelledby="panel-heading-other"> +<div class="list-group"> +<a class="list-group-item" data-id="Notifications" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Notifications" title="" +>Notifications</a> +<a class="list-group-item" data-id="Logging" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Logging" title="Review and modify the log level associated to a registered logger." +>Logging</a> +<a class="list-group-item" data-id="captcha" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=captcha" title="" +>CAPTCHA</a> +<a class="list-group-item" data-id="analytics" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=analytics" title="Configure the Google Analytics™ account." +>Google Analytics™</a> +<a class="list-group-item" data-id="MessageStream" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=MessageStream" title="Enable or disable the message stream in the wiki." +>Message Stream</a> +</div> +</div> +</div> +<div class="panel panel-default noitems hidden"> +<div class="panel-heading collapsed"> +No results. +</div> +</div> +</div><div id="admin-page-content"> + <div class="medium-avatars"> + <div class="xwiki-livetable-container"> + <table id="groupstable" class="xwiki-livetable"> + <tr> + <td class="xwiki-livetable-pagination"> + <span id="groupstable-limits" class="xwiki-livetable-limits"></span> + <span id="groupstable-pagesize" class="xwiki-livetable-pagesize"> + <span>per page of</span> + <span class="xwiki-livetable-pagesize-content" ></span> + </span> + <span id="groupstable-ajax-loader" class="xwiki-livetable-loader hidden"> + <img src="/xwiki/resources/icons/xwiki/ajax-loader-large.gif?cache-version=1581617618000" alt="Loading..." title="" /> + </span> + <span class="controlPagination"> + <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> + <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> + </span> + <span class="pagination"> + <span class="xwiki-livetable-pagination-text">Page</span> + <span class="xwiki-livetable-pagination-content" ></span> + </span> + </td> + </tr> + <tr> + <td class="xwiki-livetable-display-container"> + <table class="xwiki-livetable-display"> + <thead class="xwiki-livetable-display-header"> + <tr> + <th scope="col" class="xwiki-livetable-display-header-text + "> + <label for="xwiki-livetable-groupstable-filter-1"> Group Name + </label> </th> + <th scope="col" class="xwiki-livetable-display-header-text + "> + Members + </th> + <th scope="col" class="xwiki-livetable-display-header-text actions + "> + Actions + </th> + </tr> + <tr class="xwiki-livetable-display-filters"> + <td class="xwiki-livetable-display-header-filter"> + <input id="xwiki-livetable-groupstable-filter-1" name="name" type="text" + title="Filter for the Group Name column" /> + </td> + <td class="xwiki-livetable-display-header-filter"> + </td> + <td class="xwiki-livetable-display-header-filter"> + </td> + </tr> + <tr class="xwiki-livetable-initial-message"> + <td colspan="3"> + <div class="warningmessage">The environment prevents the table from loading data.</div> + </td> + </tr> + </thead> + <tbody id="groupstable-display" class="xwiki-livetable-display-body"><tr><td>&nbsp;</td></tr></tbody> + </table> + </td> + </tr> + <tr> + <td class="xwiki-livetable-pagination"> + <span class="xwiki-livetable-limits"></span> + <span class="controlPagination"> + <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> + <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> + </span> + <span class="pagination"> + <span class="xwiki-livetable-pagination-text">Page</span> + <span class="xwiki-livetable-pagination-content" ></span> + </span> + </td> + </tr> + </table> + <div id="groupstable-inaccessible-docs" class="hidden"> + <div class="infomessage">(*) Some pages require special rights to be viewed.</div> + </div> + <div id="groupstable-computed-title-docs" class="hidden"> + <div class="infomessage">(<span class='docTitleComputed'></span>)&nbsp;Some pages have a computed title. Filtering and sorting by title will not work as expected for these pages.</div> + </div> + <script type="text/javascript"> + //<![CDATA[ +(function() { + var startup = function(container) { + // Make sure the LiveTable code is loaded (the WYSIWYG editor doesn't load the JavaScript code for instance). + var liveTableCodeLoaded = XWiki && XWiki.widgets && XWiki.widgets.LiveTable; + // Also make sure the live table is not already initialized. + var liveTableElement = $('groupstable'); + if (liveTableCodeLoaded && liveTableElement && !liveTableElement.__liveTable && + (liveTableElement == container || liveTableElement.descendantOf(container))) { + window["livetable_groupstable"] = liveTableElement.__liveTable = new XWiki.widgets.LiveTable("/xwiki/bin/view/XWiki/XWikiPreferences?xpage=getgroups", + "groupstable", function (row, i, table) { + // This callback method has been generated from Velocity. + var columns = ["name","members","_actions"]; + var columnDescriptors = {"name":{"type":"text","html":true,"sortable":false,"headerClass":null,"displayName":"Group Name"},"members":{"filterable":false,"sortable":false,"headerClass":null,"displayName":"Members"},"scope":{"type":"list","sortable":false,"displayName":"Scope"},"_actions":{"actions":[{"id":"edit","label":"edit","async":null,"callback":null,"icon":"<span class=\"fa fa-pencil\"></span>"},{"id":"delete","label":"delete","async":null,"callback":null,"icon":"<span class=\"fa fa-times\"></span>"}],"labels":{"delete":"delete"},"filterable":false,"headerClass":"actions","displayName":"Actions"}}; + var className = ""; + var showFilterNote = false; + if (!row['doc_viewable']) { + $("groupstable-inaccessible-docs").removeClassName('hidden'); + } + var tr = new Element('tr'); + columns.forEach(function(column) { + var descriptor = columnDescriptors[column] || {}; + if (descriptor.type === 'hidden') { + return; + } + // The column's display name to be used when displaying the reponsive version. + var displayName = descriptor.displayName || column; + var fieldName = column.replace(/^doc\./, 'doc_'); + if (column === '_actions') { + var adminActions = ['admin', 'rename', 'rights']; + var td = new Element('td', { + 'class': 'actions', + 'data-title': displayName + }); + td.toggleClassName('hide-labels', descriptor.labels === false); + (descriptor.actions || []).forEach(function(action, index) { + if (row['doc_has' + action.id] || action.id === 'view' || (row['doc_has' + action.id] === undefined && + (row['doc_hasadmin'] || adminActions.indexOf(action.id) < 0))) { + var link = new Element('a', { + 'href': row['doc_' + action.id + '_url'] || row['doc_url'], + 'class': 'action action' + action.id + }).update('<span class="action-icon"></span><span class="action-label"></span>'); + link.down('.action-icon').update(action.icon).writeAttribute('title', action.label); + link.down('.action-label').update(action.label.escapeHTML()); + if (action.async) { + link.observe('click', function(event) { + event.stop(); + new Ajax.Request(this.href, { + onSuccess: function() { + eval(action.callback); + } + }); + }.bindAsEventListener(link)); + } + td.insert(link); + } + }); + tr.appendChild(td); + } else { + var td = new Element('td', { + 'class': [ + fieldName, + 'link' + (descriptor.link || ''), + 'type' + (descriptor.type || '') + ].join(' '), + 'data-title': displayName + }); + var container = td; + if (descriptor.link && row['doc_viewable']) { + var link = new Element(descriptor.link === 'editor' ? 'span' : 'a'); + // Automatic: the link URL is in JSON results, with the '_url' sufix. + if (descriptor.link === 'auto') { + link.href = row[fieldName + '_url'] || row['doc_url']; + } else if (descriptor.link === 'field') { + if (row[fieldName + '_url']) { + link.href = row[fieldName + '_url']; + } + // Property editor + } else if (descriptor.link === 'editor') { + var propertyClassName = descriptor['class'] || className; + td.observe('click', function(event) { + var tag = event.element().down('span') || event.element(); + editProperty(row['doc_fullName'], propertyClassName, column, function(value) { + tag.innerHTML = value; + }); + }); + // Author, space or wiki link. + } else if (row['doc_' + descriptor.link + '_url']) { + link.href = row['doc_' + descriptor.link + '_url']; + } else { + link.href = row['doc_url']; + } + td.appendChild(link); + container = link; + } + // The value can be passed as a string.. + if (descriptor.html + '' === 'true') { + container.innerHTML = row[fieldName] || ''; + } else if (row[fieldName] !== undefined && row[fieldName] !== null) { + var text = row[fieldName] + ''; + if (fieldName === 'doc_name' && !row['doc_viewable']) { + text += '*'; + } + if (showFilterNote && fieldName === 'doc_title' && row['doc_title_raw'] !== undefined) { + container.addClassName('docTitleComputed'); + } + container.update(text.escapeHTML()); + } + tr.appendChild(td); + } + }); + return tr; +} +, {"maxPages":10,"limit":15,"selectedTags":[]}); + document.observe("xwiki:livetable:groupstable:loadingEntries", function() { + $('groupstable-pagesize').addClassName("hidden"); + }); + document.observe("xwiki:livetable:groupstable:loadingComplete", function() { + $('groupstable-pagesize').removeClassName("hidden"); + }); + return true; + } + return false; + }; + var init = function(event) { + var elements = (event && event.memo.elements) || [$('body')]; + return elements.length > 0 && elements.some(startup); + }; + // Initialize the live table on page load or after the live table code has been loaded. + (XWiki && XWiki.isInitialized && init()) || document.observe('xwiki:livetable:loading', init); + // Initialize the live table when it is added after the page is loaded. + document.observe('xwiki:dom:updated', init); +})(); + //]]> + </script> +</div></div> +<p> +<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#createGroupModal"> +Create group +</button> +</p> +<div class="modal" id="createGroupModal" tabindex="-1" role="dialog" +aria-labelledby="createGroupModal-label" data-backdrop="static" data-keyboard="false"> +<div class="modal-dialog" role="document"> +<form class="modal-content xform"> +<div class="modal-header"> +<button type="button" class="close" data-dismiss="modal" aria-label="Close"> +<span aria-hidden="true">&times;</span> +</button> +<div class="modal-title" id="createGroupModal-label"> +Create group +</div> +</div> +<div class="modal-body"> +<div class="hidden"> +<input type="hidden" name="form_token" value="epgA3yUHjVWOMi0Zxgi4eQ" /> +<input type="hidden" name="template" value="XWiki.XWikiGroupTemplate" /> +</div> +<dl> +<dt> +<label for="createGroupModal-groupName" class="sr-only"> +Group Name +</label> +</dt> +<dd class="form-group has-feedback"> +<input type="text" class="form-control" id="createGroupModal-groupName" name="name" autocomplete="off" +placeholder="Group Name" /> +<span class="form-control-feedback loading hidden" aria-hidden="true"></span> +<span class="form-control-feedback success hidden" aria-hidden="true"><span class="fa fa-check"></span></span> +<span class="form-control-feedback error hidden" aria-hidden="true"><span class="fa fa-times"></span></span> +<span class="help-block hidden"></span> +</dd> +</dl> +</div> +<div class="modal-footer"> +<button type="button" class="btn btn-default" data-dismiss="modal"> +Cancel +</button> +<button type="submit" class="btn btn-primary"> +Create +</button> +</div> +</form> +</div> +</div> + +<div class="modal" id="editGroupModal" tabindex="-1" role="dialog" aria-labelledby="editGroupModal-label" +data-backdrop="static" data-keyboard="false" data-liveTable="#groupstable" data-liveTableAction="edit"> +<div class="modal-dialog" role="document"> +<div class="modal-content"> +<div class="modal-header"> +<button type="button" class="close" data-dismiss="modal" aria-label="Close"> +<span aria-hidden="true">&times;</span> +</button> +<div class="modal-title" id="editGroupModal-label"> +Edit group +</div> +</div> +<div class="modal-body"></div> +</div> +</div> +</div> + +<div class="modal" id="deleteGroupModal" tabindex="-1" role="dialog" aria-labelledby="deleteGroupModal-label" +data-liveTable="#groupstable" data-liveTableAction="delete"> +<div class="modal-dialog" role="document"> +<div class="modal-content"> +<div class="modal-header"> +<button type="button" class="close" data-dismiss="modal" aria-label="Close"> +<span aria-hidden="true">&times;</span> +</button> +<div class="modal-title" id="deleteGroupModal-label"> +Delete group +</div> +</div> +<div class="modal-body"> +<p>The group <span class="groupName"></span> will be deleted. Are you sure you want to proceed?</p> +</div> +<div class="modal-footer"> +<button type="button" class="btn btn-default" data-dismiss="modal"> +Cancel +</button> +<button type="submit" class="btn btn-danger" data-dismiss="modal"> +Delete +</button> +</div> +</div> +</div> +</div> +</div> + <div class="clearfloats"></div> +</div></div></div><div id="leftPanels" class="panels left panel-width-Medium"> + <div class="panel expanded PanelsApplications Applications"><h1 class="xwikipaneltitle">Applications</h1><div class="xwikipanelcontents"><ul class="applicationsPanel nav nav-pills nav-stacked"> +<li> +<a href="/xwiki/bin/view/Dashboard/" title="Dashboard"> +<span class="application-img"><span class="fa fa-th-large"></span></span> +<span class="application-label">Dashboard</span> +</a> +</li> +<li> +<a href="/xwiki/bin/view/Help/" title="Help"> +<span class="application-img"><span class="fa fa-question-circle"></span></span> +<span class="application-label">Help</span> +</a> +</li> +<li> +<a href="/xwiki/bin/view/Sandbox/" title="Sandbox"> +<span class="application-img"><span class="fa fa-coffee"></span></span> +<span class="application-label">Sandbox</span> +</a> +</li> +</ul> +<ul class="applicationsPanel applicationsPanelMoreList nav nav-pills nav-stacked"> +<li> +<a class="applicationPanelMoreButton" href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.AddExtensions" title="More applications"> +<span class="application-img"><span class="fa fa-plus"></span></span> +<span class="application-label">More applications</span> +</a> +<div class="applicationPanelMoreContainer hidden"> +<ul class="nav nav-pills nav-stacked"> +<li> +<a href="/xwiki/bin/view/AppWithinMinutes/" title="Create your own!"> +<span class="application-img"><span class="fa fa-caret-right"></span></span> +<span class="application-label">Create your own!</span> +</a> +</li> +<li> +<a href="/xwiki/bin/view/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.Extensions" title="Install new applications"> +<span class="application-img"><span class="fa fa-caret-right"></span></span> +<span class="application-label">Install new applications</span> +</a> +</li> +</ul> +</div> +</li> +</ul></div></div> + <div class="panel expanded PanelsNavigation Navigation"><h1 class="xwikipaneltitle">Navigation</h1><div class="xwikipanelcontents"> + + + + + + + + <div class = "xtree" data-responsive = "true" data-url = "/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&#38;sheet=XWiki.DocumentTree&#38;showAttachments=false&#38;showTranslations=false&#38;exclusions=document%3Axwiki%3ASandbox.WebHome&#38;exclusions=document%3Axwiki%3AHelp.WebHome&#38;exclusions=document%3Axwiki%3AMenu.WebHome&#38;exclusions=document%3Axwiki%3AXWiki.WebHome" data-dragAndDrop = "false" data-contextMenu = "false" data-icons = "false" data-edges = "false" data-checkboxes = "false" data-openTo = "document:xwiki:XWiki.XWikiPreferences" data-finder = "false" ></div></div></div> + </div> + + </div><div id="rightPanels" class="panels right panel-width-Medium"> + <div class="xwiki-async" data-xwiki-async-id="uix/xwiki%3AHelp.TipsPanel.WebHome/author/xwiki%3AXWiki.superadmin/locale/en/secureDocument/xwiki%3AHelp.TipsPanel.WebHome/9" data-xwiki-async-client-id="9"></div> + + <div class="panel expanded HelpSupportPanel WebHome"><h1 class="xwikipaneltitle">Need help?</h1><div class="xwikipanelcontents"><div class="SupportPanel"><p>If you need help with XWiki you can contact:</p><ul><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HCommunitySupport">Community Support</a></span></li><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HProfessionalSupport">Professional Support</a></span></li></ul></div></div></div> + </div> + +<div class="clearfloats"></div> + </div></div><div id="footerglobal"> + <div id="xwikilicence"></div> + <div id="xwikiplatformversion"> + <a href="http://extensions.xwiki.org?id=org.xwiki.platform:xwiki-platform-distribution-jetty-hsqldb:12.1-SNAPSHOT:::/xwiki-commons-pom/xwiki-platform/xwiki-platform-distribution/xwiki-platform-distribution-jetty-hsqldb"> + XWiki Jetty HSQLDB 12.1-SNAPSHOT + </a> + </div> + </div> + +</div></div></body> +</html> \ No newline at end of file From 1d9f465895af9141e361b276322fc0bdb323b566 Mon Sep 17 00:00:00 2001 From: kalaider <a.kalaider@yandex.ru> Date: Fri, 14 Feb 2020 00:40:19 +0300 Subject: [PATCH 399/774] Apply attribute normalization to void tags --- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 22cf0bcef7..cfd7ef1b91 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -231,7 +231,7 @@ void insert(Element el) { Element insertEmpty(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name(), settings); - Element el = new Element(tag, null, startTag.attributes); + Element el = new Element(tag, null, settings.normalizeAttributes(startTag.attributes)); insertNode(el); if (startTag.isSelfClosing()) { if (tag.isKnownTag()) { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 8a145e43ed..001950cfa9 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1090,6 +1090,12 @@ public void testInvalidTableContents() throws IOException { assertEquals("<tag>One</tag>", TextUtil.stripNewlines(div.nextElementSibling().outerHtml())); } + @Test public void testHtmlLowerCaseOfVoidTags() { + String html = "<!doctype HTML><IMG ALT=One></DIV>"; + Document doc = Jsoup.parse(html); + assertEquals("<!doctype html> <html> <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <img alt=\"One\"> </body> </html>", StringUtil.normaliseWhitespace(doc.outerHtml())); + } + @Test public void canPreserveTagCase() { Parser parser = Parser.htmlParser(); parser.settings(new ParseSettings(true, false)); From 6e8241ab59152f1e87a9276654c67534124793c7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 11:58:24 -0800 Subject: [PATCH 400/774] Normalize form attributes --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 8 +++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 36d32d6b90..a93b211a6b 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ jsoup changelog marked, a bufferUp occurred, and then the reader was rewound. <https://github.com/jhy/jsoup/issues/1324> + * Bugfix: empty tags and form tags did not have their attributes normalized (lower-cased by default) + <https://github.com/jhy/jsoup/pull/1323> + * Removed old methods and classes that were marked deprecated in previous releases. **** Release 1.12.2 [2020-Feb-08] diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index cfd7ef1b91..f161be0de5 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -246,7 +246,7 @@ Element insertEmpty(Token.StartTag startTag) { FormElement insertForm(Token.StartTag startTag, boolean onStack) { Tag tag = Tag.valueOf(startTag.name(), settings); - FormElement el = new FormElement(tag, null, startTag.attributes); + FormElement el = new FormElement(tag, null, settings.normalizeAttributes(startTag.attributes)); setFormElement(el); insertNode(el); if (onStack) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 001950cfa9..6327b385c4 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1090,12 +1090,18 @@ public void testInvalidTableContents() throws IOException { assertEquals("<tag>One</tag>", TextUtil.stripNewlines(div.nextElementSibling().outerHtml())); } - @Test public void testHtmlLowerCaseOfVoidTags() { + @Test public void testHtmlLowerCaseAttributesOfVoidTags() { String html = "<!doctype HTML><IMG ALT=One></DIV>"; Document doc = Jsoup.parse(html); assertEquals("<!doctype html> <html> <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <img alt=\"One\"> </body> </html>", StringUtil.normaliseWhitespace(doc.outerHtml())); } + @Test public void testHtmlLowerCaseAttributesForm() { + String html = "<form NAME=one>"; + Document doc = Jsoup.parse(html); + assertEquals("<form name=\"one\"></form>", StringUtil.normaliseWhitespace(doc.body().html())); + } + @Test public void canPreserveTagCase() { Parser parser = Parser.htmlParser(); parser.settings(new ParseSettings(true, false)); From 006c6cc5cb279245b57a4327327ecded17a77849 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 12:08:14 -0800 Subject: [PATCH 401/774] Indent sanity test --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 6327b385c4..5f892d7ba6 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1278,6 +1278,16 @@ public void testNoSpuriousSpace() { assertEquals("JustOneTwo", doc.body().text()); } + @Test + public void pTagsGetIndented() { + String html = "<div><p><a href=one>One</a><p><a href=two>Two</a></p></div>"; + Document doc = Jsoup.parse(html); + assertEquals("<div>\n" + + " <p><a href=\"one\">One</a></p>\n" + + " <p><a href=\"two\">Two</a></p>\n" + + "</div>", doc.body().html()); + } + @Test public void testH20() { // https://github.com/jhy/jsoup/issues/731 From f4338e20a5e439632e3d2fc8fa3a330ade6d02ad Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 13:06:52 -0800 Subject: [PATCH 402/774] Indent preserve-upper-cased tags correctly --- CHANGES | 2 ++ src/main/java/org/jsoup/parser/Tag.java | 19 ++++++++++--- .../java/org/jsoup/parser/HtmlParserTest.java | 27 ++++++++++++++++--- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index a93b211a6b..23452adc92 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,8 @@ jsoup changelog * Bugfix: empty tags and form tags did not have their attributes normalized (lower-cased by default) <https://github.com/jhy/jsoup/pull/1323> + * Bugfix: when preserve case was set to on, the HTML pretty-print formatter didn't indent capitalized tags correctly. + * Removed old methods and classes that were marked deprecated in previous releases. **** Release 1.12.2 [2020-Feb-08] diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 14270635a0..7bb84740d1 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -11,7 +11,7 @@ * * @author Jonathan Hedley, jonathan@hedley.net */ -public class Tag { +public class Tag implements Cloneable { private static final Map<String, Tag> tags = new HashMap<>(); // map of known tags private String tagName; @@ -61,14 +61,18 @@ public static Tag valueOf(String tagName, ParseSettings settings) { Tag tag = tags.get(tagName); if (tag == null) { - tagName = settings.normalizeTag(tagName); + tagName = settings.normalizeTag(tagName); // the name we'll use Validate.notEmpty(tagName); - tag = tags.get(tagName); + String normalName = Normalizer.lowerCase(tagName); // the lower-case name to get tag settings off + tag = tags.get(normalName); if (tag == null) { // not defined: create default; go anywhere, do anything! (incl be inside a <p>) tag = new Tag(tagName); tag.isBlock = false; + } else if (settings.preserveTagCase() && !tagName.equals(normalName)) { + tag = tag.clone(); // get a new version vs the static one, so name update doesn't reset all + tag.tagName = tagName; } } return tag; @@ -216,6 +220,15 @@ public String toString() { return tagName; } + @Override + protected Tag clone() { + try { + return (Tag) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + // internal static initialisers: // prepped from http://www.w3.org/TR/REC-html40/sgml/dtd.html and other sources private static final String[] blockTags = { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 5f892d7ba6..601d40b498 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.util.List; +import static org.jsoup.parser.ParseSettings.preserveCase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -69,7 +70,7 @@ public class HtmlParserTest { @Test public void retainsAttributesOfDifferentCaseIfSensitive() { String html = "<p One=One One=Two one=Three two=Four two=Five Two=Six>Text</p>"; - Parser parser = Parser.htmlParser().settings(ParseSettings.preserveCase); + Parser parser = Parser.htmlParser().settings(preserveCase); Document doc = parser.parseInput(html, ""); assertEquals("<p One=\"One\" one=\"Three\" two=\"Four\" Two=\"Six\">Text</p>", doc.selectFirst("p").outerHtml()); } @@ -1143,7 +1144,7 @@ public void testInvalidTableContents() throws IOException { @Test public void caseSensitiveParseTree() { String html = "<r><X>A</X><y>B</y></r>"; Parser parser = Parser.htmlParser(); - parser.settings(ParseSettings.preserveCase); + parser.settings(preserveCase); Document doc = parser.parseInput(html, ""); assertEquals("<r> <X> A </X> <y> B </y> </r>", StringUtil.normaliseWhitespace(doc.body().html())); } @@ -1158,9 +1159,9 @@ public void testInvalidTableContents() throws IOException { @Test public void preservedCaseLinksCantNest() { String html = "<A>ONE <A>Two</A></A>"; Document doc = Parser.htmlParser() - .settings(ParseSettings.preserveCase) + .settings(preserveCase) .parseInput(html, ""); - assertEquals("<A> ONE </A><A> Two </A>", StringUtil.normaliseWhitespace(doc.body().html())); + assertEquals("<A>ONE </A><A>Two</A>", StringUtil.normaliseWhitespace(doc.body().html())); } @Test public void normalizesDiscordantTags() { @@ -1288,6 +1289,24 @@ public void pTagsGetIndented() { "</div>", doc.body().html()); } + @Test + public void indentRegardlessOfCase() { + String html = "<p>1</p><P>2</P>"; + Document doc = Jsoup.parse(html); + assertEquals( + "<body>\n" + + " <p>1</p>\n" + + " <p>2</p>\n" + + "</body>", doc.body().outerHtml()); + + Document caseDoc = Jsoup.parse(html, "", Parser.htmlParser().settings(preserveCase)); + assertEquals( + "<body>\n" + + " <p>1</p>\n" + + " <P>2</P>\n" + + "</body>", caseDoc.body().outerHtml()); + } + @Test public void testH20() { // https://github.com/jhy/jsoup/issues/731 From c5c5a735d3d3f643ee614e9f9f93a0978970cf20 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 20:55:12 -0800 Subject: [PATCH 403/774] Close and clear the character reader Saves a bunch of retained memory in Document (and transitively, every Element) #1321 --- CHANGES | 6 ++++-- .../org/jsoup/parser/CharacterReader.java | 19 ++++++++++++++++--- .../java/org/jsoup/parser/TreeBuilder.java | 7 +++++++ .../java/org/jsoup/parser/HtmlParserTest.java | 11 ++++++++--- .../org/jsoup/parser/XmlTreeBuilderTest.java | 11 +++++++++-- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 23452adc92..794f69d7b5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,10 @@ jsoup changelog *** Release 1.13.1 [PENDING] - * Improvement: decent memory optimization by not creating Attribute objects for each Element unless/until the Element - gets an attribute. + * Improvement: memory optimizations, reducing the retained size of a Document by ~ 39%, and allocations by ~ 5%: + 1. Attributes holder in Elements is only created if the element has attributes + 2. Only track the baseUri in an element when it is set via DOM to a new value for a given tree + 3. After parsing, do not retain the input character reader (and associated buffers) in the Document#parser * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 54f7cfd45a..a5df335233 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -19,14 +19,14 @@ public final class CharacterReader { static final int readAheadLimit = (int) (maxBufferLen * 0.75); // visible for testing private static final int minReadAheadLen = 1024; // the minimum mark length supported. No HTML entities can be larger than this. - private final char[] charBuf; - private final Reader reader; + private char[] charBuf; + private Reader reader; private int bufLength; private int bufSplitPoint; private int bufPos; private int readerPos; private int bufMark = -1; - private final String[] stringCache = new String[512]; // holds reused strings in this doc, to lessen garbage + private String[] stringCache = new String[512]; // holds reused strings in this doc, to lessen garbage public CharacterReader(Reader input, int sz) { Validate.notNull(input); @@ -44,6 +44,19 @@ public CharacterReader(String input) { this(new StringReader(input), input.length()); } + public void close() { + if (reader == null) + return; + try { + reader.close(); + } catch (IOException ignored) { + } finally { + reader = null; + charBuf = null; + stringCache = null; + } + } + private boolean readFully; // if the underlying stream has been completely read, no value in further buffering private void bufferUp() { if (readFully || bufPos < bufSplitPoint) diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index b54f06aa73..00c4d95ac8 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -45,6 +45,13 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { Document parse(Reader input, String baseUri, Parser parser) { initialiseParse(input, baseUri, parser); runParser(); + + // tidy up - as the Parser and Treebuilder are retained in document for settings / fragments + reader.close(); + reader = null; + tokeniser = null; + stack = null; + return doc; } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 601d40b498..63c3019e17 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -24,9 +24,7 @@ import java.util.List; import static org.jsoup.parser.ParseSettings.preserveCase; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Tests for the Parser @@ -1354,4 +1352,11 @@ public void testUNewlines() { assertEquals("<optgroup label=\"a\"> <option>one </option><option>two </option><option>three </option></optgroup><optgroup label=\"b\"> <option>four </option><option>fix </option><option>six </option></optgroup>", select.html()); } + + @Test public void readerClosedAfterParse() { + Document doc = Jsoup.parse("Hello"); + TreeBuilder treeBuilder = doc.parser().getTreeBuilder(); + assertNull(treeBuilder.reader); + assertNull(treeBuilder.tokeniser); + } } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 011e708ca9..490dee0ea5 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -22,8 +22,8 @@ import java.util.List; import static org.jsoup.nodes.Document.OutputSettings.Syntax; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; /** * Tests XmlTreeBuilder. @@ -255,4 +255,11 @@ public void handlesLTinScript() { assertEquals("<p One=\"One\" ONE=\"Two\" one=\"Three\" two=\"Six\" Two=\"Eight\">Text</p>", doc.selectFirst("p").outerHtml()); } + @Test public void readerClosedAfterParse() { + Document doc = Jsoup.parse("Hello", "", Parser.xmlParser()); + TreeBuilder treeBuilder = doc.parser().getTreeBuilder(); + assertNull(treeBuilder.reader); + assertNull(treeBuilder.tokeniser); + } + } From 532360506bdc20ed6ae71d4ca16effd078a18266 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 21:13:29 -0800 Subject: [PATCH 404/774] Script and Style must be DataNodes when case-sensitive parsing --- CHANGES | 6 +++++- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 10 ++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 794f69d7b5..bf6310fb87 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,8 @@ jsoup changelog if an attribute is set but has no value. Useful in place of the deprecated and removed BooleanAttribute class and instanceof test. + * Improvement: removed old methods and classes that were marked deprecated in previous releases. + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> @@ -24,7 +26,9 @@ jsoup changelog * Bugfix: when preserve case was set to on, the HTML pretty-print formatter didn't indent capitalized tags correctly. - * Removed old methods and classes that were marked deprecated in previous releases. + * Bugfix: ensure that script and style contents are parsed into DataNodes, not TextNodes, when in case-sensitive + parse mode. + **** Release 1.12.2 [2020-Feb-08] * Improvement: the :has() selector now supports relative selectors. For example, the query diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index f161be0de5..ed203cf5b1 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -262,7 +262,7 @@ void insert(Token.Comment commentToken) { void insert(Token.Character characterToken) { final Node node; final Element el = currentElement(); - final String tagName = el.tagName(); + final String tagName = el.normalName(); final String data = characterToken.getData(); if (characterToken.isCData()) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 63c3019e17..adf9a45da4 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1359,4 +1359,14 @@ public void testUNewlines() { assertNull(treeBuilder.reader); assertNull(treeBuilder.tokeniser); } + + @Test public void scriptInDataNode() { + Document doc = Jsoup.parse("<script>Hello</script><style>There</style>"); + assertTrue(doc.selectFirst("script").childNode(0) instanceof DataNode); + assertTrue(doc.selectFirst("style").childNode(0) instanceof DataNode); + + doc = Jsoup.parse("<SCRIPT>Hello</SCRIPT><STYLE>There</STYLE>", "", Parser.htmlParser().settings(preserveCase)); + assertTrue(doc.selectFirst("script").childNode(0) instanceof DataNode); + assertTrue(doc.selectFirst("style").childNode(0) instanceof DataNode); + } } From 75815e2d6b743bbdffede8e46d80da5c9684385c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 21:22:02 -0800 Subject: [PATCH 405/774] Use Tag#normalName, not Element#tagName for all comparisons --- src/main/java/org/jsoup/examples/ListLinks.java | 2 +- src/main/java/org/jsoup/nodes/Element.java | 4 ++-- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/main/java/org/jsoup/safety/Cleaner.java | 2 +- src/test/java/org/jsoup/parser/HtmlParserTest.java | 9 +++++++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/examples/ListLinks.java b/src/main/java/org/jsoup/examples/ListLinks.java index 64b29ba107..935bd14fb5 100644 --- a/src/main/java/org/jsoup/examples/ListLinks.java +++ b/src/main/java/org/jsoup/examples/ListLinks.java @@ -24,7 +24,7 @@ public static void main(String[] args) throws IOException { print("\nMedia: (%d)", media.size()); for (Element src : media) { - if (src.tagName().equals("img")) + if (src.normalName().equals("img")) print(" * %s: <%s> %sx%s (%s)", src.tagName(), src.attr("abs:src"), src.attr("width"), src.attr("height"), trim(src.attr("alt"), 20)); diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 799433a90c..94a21af8b1 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1401,7 +1401,7 @@ public Element toggleClass(String className) { * @return the value of the form element, or empty string if not set. */ public String val() { - if (tagName().equals("textarea")) + if (normalName().equals("textarea")) return text(); else return attr("value"); @@ -1413,7 +1413,7 @@ public String val() { * @return this element (for chaining) */ public Element val(String value) { - if (tagName().equals("textarea")) + if (normalName().equals("textarea")) text(value); else attr("value", value); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index ed203cf5b1..6b35a58d75 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -92,7 +92,7 @@ List<Node> parseFragment(String inputFragment, Element context, String baseUri, doc.quirksMode(context.ownerDocument().quirksMode()); // initialise the tokeniser state: - String contextTag = context.tagName(); + String contextTag = context.normalName(); if (StringUtil.in(contextTag, "title", "textarea")) tokeniser.transition(TokeniserState.Rcdata); else if (StringUtil.in(contextTag, "iframe", "noembed", "noframes", "style", "xmp")) diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 0d3c7e9fcb..22ecb0b332 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -107,7 +107,7 @@ public void head(Node source, int depth) { if (source instanceof Element) { Element sourceEl = (Element) source; - if (whitelist.isSafeTag(sourceEl.tagName())) { // safe, clone and copy safe attrs + if (whitelist.isSafeTag(sourceEl.normalName())) { // safe, clone and copy safe attrs ElementMeta meta = createSafeElement(sourceEl); Element destChild = meta.el; destination.appendChild(destChild); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index adf9a45da4..d41144ca28 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1369,4 +1369,13 @@ public void testUNewlines() { assertTrue(doc.selectFirst("script").childNode(0) instanceof DataNode); assertTrue(doc.selectFirst("style").childNode(0) instanceof DataNode); } + + @Test public void textareaValue() { + String html = "<TEXTAREA>YES YES</TEXTAREA>"; + Document doc = Jsoup.parse(html); + assertEquals("YES YES", doc.selectFirst("textarea").val()); + + doc = Jsoup.parse(html, "", Parser.htmlParser().settings(preserveCase)); + assertEquals("YES YES", doc.selectFirst("textarea").val()); + } } From f54857ed7a0423a0d5482b33308bc3d1a65bb97e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 15 Feb 2020 22:04:06 -0800 Subject: [PATCH 406/774] Sampling found avg num attributes is 1.49 Saves more allocations --- CHANGES | 2 +- src/main/java/org/jsoup/nodes/Attributes.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bf6310fb87..8530dac768 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ jsoup changelog *** Release 1.13.1 [PENDING] - * Improvement: memory optimizations, reducing the retained size of a Document by ~ 39%, and allocations by ~ 5%: + * Improvement: memory optimizations, reducing the retained size of a Document by ~ 39%, and allocations by ~ 9%: 1. Attributes holder in Elements is only created if the element has attributes 2. Only track the baseUri in an element when it is set via DOM to a new value for a given tree 3. After parsing, do not retain the input character reader (and associated buffers) in the Document#parser diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 3e9fc6b385..2d7def8c35 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -36,7 +36,7 @@ public class Attributes implements Iterable<Attribute>, Cloneable { // Indicates a jsoup internal key. Can't be set via HTML. (It could be set via accessor, but not too worried about // that. Suppressed from list, iter. static final char InternalPrefix = '/'; - private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting + private static final int InitialCapacity = 2; // sampling found mean count when attrs present = 1.49; 1.08 overall. 2.6:1 have attrs. // manages the key/val arrays private static final int GrowthFactor = 2; From 265653e3f2e04321fa327a5a32241de70cdbe234 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 16 Feb 2020 10:33:04 -0800 Subject: [PATCH 407/774] Use URL instead of email --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d8e9f6609c..1802c6faeb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2009-2020 Jonathan Hedley <jonathan@hedley.net> +Copyright (c) 2009-2020 Jonathan Hedley <https://jsoup.org/> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 7a405918f4fdf71ebc2a8b07ce3f6fb09f987c0a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 16 Feb 2020 21:59:39 -0800 Subject: [PATCH 408/774] Minor (~2%) increase in cache hit rate By including length in the hash function. I tried various changes including 2048 cache size, or not replacing conflicts. Larger did not give a hit rate improvement commensurate to the extra size, and not replacing conflicts led to a significant drop to the hit rate. --- src/main/java/org/jsoup/parser/CharacterReader.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index a5df335233..ef1590bc48 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -26,7 +26,8 @@ public final class CharacterReader { private int bufPos; private int readerPos; private int bufMark = -1; - private String[] stringCache = new String[512]; // holds reused strings in this doc, to lessen garbage + private static final int stringCacheSize = 512; + private String[] stringCache = new String[stringCacheSize]; // holds reused strings in this doc, to lessen garbage public CharacterReader(Reader input, int sz) { Validate.notNull(input); @@ -520,6 +521,8 @@ boolean containsIgnoreCase(String seq) { @Override public String toString() { + if (bufLength - bufPos < 0) + return ""; return new String(charBuf, bufPos, bufLength - bufPos); } @@ -538,14 +541,14 @@ private static String cacheString(final char[] charBuf, final String[] stringCac return ""; // calculate hash: - int hash = 0; + int hash = 31 * count; int offset = start; for (int i = 0; i < count; i++) { hash = 31 * hash + charBuf[offset++]; } // get from cache - final int index = hash & stringCache.length - 1; + final int index = hash & stringCacheSize - 1; String cached = stringCache[index]; if (cached == null) { // miss, add From bd64b0192c7d3485665afdc10805b587df19eab3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 18 Feb 2020 20:20:39 -0800 Subject: [PATCH 409/774] Added Element#closest And Evaluator enabled select and selectFirst. --- CHANGES | 7 ++ src/main/java/org/jsoup/nodes/Element.java | 77 ++++++++++++++++--- .../java/org/jsoup/select/QueryParser.java | 1 + .../java/org/jsoup/nodes/ElementTest.java | 43 ++++++++++- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 8530dac768..0f11b429d3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.13.1 [PENDING] + * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the + selector. + * Improvement: memory optimizations, reducing the retained size of a Document by ~ 39%, and allocations by ~ 9%: 1. Attributes holder in Elements is only created if the element has attributes 2. Only track the baseUri in an element when it is set via DOM to a new value for a given tree @@ -14,6 +17,10 @@ jsoup changelog * Improvement: removed old methods and classes that were marked deprecated in previous releases. + * Improvement: added Element#select(Evaluator) and Element#selectFirst(Evaluator), to allow re-use of a parsed CSS + selector if using the same evaluator many times. + <https://github.com/jhy/jsoup/issues/1319> + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 94a21af8b1..583f7d1a04 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -377,31 +377,42 @@ public List<DataNode> dataNodes() { /** * Find elements that match the {@link Selector} CSS query, with this element as the starting context. Matched elements * may include this element, or any of its children. - * <p> - * This method is generally more powerful to use than the DOM-type {@code getElementBy*} methods, because - * multiple filters can be combined, e.g.: - * </p> + * <p>This method is generally more powerful to use than the DOM-type {@code getElementBy*} methods, because + * multiple filters can be combined, e.g.:</p> * <ul> * <li>{@code el.select("a[href]")} - finds links ({@code a} tags with {@code href} attributes) * <li>{@code el.select("a[href*=example.com]")} - finds links pointing to example.com (loosely) * </ul> - * <p> - * See the query syntax documentation in {@link org.jsoup.select.Selector}. - * </p> + * <p>See the query syntax documentation in {@link org.jsoup.select.Selector}.</p> + * <p>Also known as {@code querySelectorAll()} in the Web DOM.</p> * * @param cssQuery a {@link Selector} CSS-like query - * @return elements that match the query (empty if none match) - * @see org.jsoup.select.Selector + * @return an {@link Elements} list containing elements that match the query (empty if none match) + * @see Selector selector query syntax + * @see QueryParser#parse(String) * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. */ public Elements select(String cssQuery) { return Selector.select(cssQuery, this); } + /** + * Find elements that match the supplied Evaluator. This has the same functionality as {@link #select(String)}, but + * may be useful if you are running the same query many times (on many documents) and want to save the overhead of + * repeatedly parsing the CSS query. + * @param evaluator an element evaluator + * @return an {@link Elements} list containing elements that match the query (empty if none match) + */ + public Elements select(Evaluator evaluator) { + return Selector.select(evaluator, this); + } + + /** * Find the first Element that matches the {@link Selector} CSS query, with this element as the starting context. * <p>This is effectively the same as calling {@code element.select(query).first()}, but is more efficient as query * execution stops on the first hit.</p> + * <p>Also known as {@code querySelector()} in the Web DOM.</p> * @param cssQuery cssQuery a {@link Selector} CSS-like query * @return the first matching element, or <b>{@code null}</b> if there is no match. */ @@ -410,7 +421,21 @@ public Element selectFirst(String cssQuery) { } /** - * Check if this element matches the given {@link Selector} CSS query. + * Finds the first Element that matches the supplied Evaluator, with this element as the starting context, or + * {@code null} if none match. + * + * @param evaluator an element evaluator + * @return the first matching element (walking down the tree, starting from this element), or {@code null} if none + * matchn. + */ + public Element selectFirst(Evaluator evaluator) { + return Collector.findFirst(evaluator, this); + } + + /** + * Checks if this element matches the given {@link Selector} CSS query. Also knows as {@code matches()} in the Web + * DOM. + * * @param cssQuery a {@link Selector} CSS query * @return if this element matches the query */ @@ -424,7 +449,37 @@ public boolean is(String cssQuery) { * @return if this element matches */ public boolean is(Evaluator evaluator) { - return evaluator.matches((Element)this.root(), this); + return evaluator.matches(this.root(), this); + } + + /** + * Find the closest element up the tree of parents that matches the specified CSS query. Will return itself, an + * ancestor, or {@code null} if there is no such matching element. + * @param cssQuery a {@link Selector} CSS query + * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not + * found. + */ + public Element closest(String cssQuery) { + return closest(QueryParser.parse(cssQuery)); + } + + /** + * Find the closest element up the tree of parents that matches the specified evaluator. Will return itself, an + * ancestor, or {@code null} if there is no such matching element. + * @param evaluator a query evaluator + * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not + * found. + */ + public Element closest(Evaluator evaluator) { + Validate.notNull(evaluator); + Element el = this; + final Element root = root(); + do { + if (evaluator.matches(root, el)) + return el; + el = el.parent(); + } while (el != null); + return null; } /** diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 872c770bbd..001daf4edd 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -37,6 +37,7 @@ private QueryParser(String query) { * Parse a CSS query into an Evaluator. * @param query CSS query * @return Evaluator + * @see Selector selector query syntax */ public static Evaluator parse(String query) { try { diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index a4ff58f9dc..5b1877f31f 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -4,8 +4,10 @@ import org.jsoup.TextUtil; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; +import org.jsoup.select.Evaluator; import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeVisitor; +import org.jsoup.select.QueryParser; import org.junit.Test; import java.util.ArrayList; @@ -1086,6 +1088,7 @@ public void testIs() { assertTrue(p.is("p")); assertFalse(p.is("div")); assertTrue(p.is("p:has(a)")); + assertFalse(p.is("a")); // does not descend assertTrue(p.is("p:first-child")); assertFalse(p.is("p:last-child")); assertTrue(p.is("*")); @@ -1100,10 +1103,48 @@ public void testIs() { assertFalse(q.is("a")); } + @Test + public void testEvalMethods() { + Document doc = Jsoup.parse("<div><p>One <a class=big>Two</a> Three</p><p>Another</p>"); + Element p = doc.selectFirst(QueryParser.parse(("p"))); + assertEquals("One Three", p.ownText()); + + assertTrue(p.is(QueryParser.parse("p"))); + Evaluator aEval = QueryParser.parse("a"); + assertFalse(p.is(aEval)); + + Element a = p.selectFirst(aEval); + assertEquals("div", a.closest(QueryParser.parse("div:has( > p)")).tagName()); + Element body = p.closest(QueryParser.parse("body")); + assertEquals("body", body.nodeName()); + } + + @Test + public void testClosest() { + String html = "<article>\n" + + " <div id=div-01>Here is div-01\n" + + " <div id=div-02>Here is div-02\n" + + " <div id=div-03>Here is div-03</div>\n" + + " </div>\n" + + " </div>\n" + + "</article>"; + + Document doc = Jsoup.parse(html); + Element el = doc.selectFirst("#div-03"); + assertEquals("Here is div-03", el.text()); + assertEquals("div-03", el.id()); + + assertEquals("div-02", el.closest("#div-02").id()); + assertEquals(el, el.closest("div div")); // closest div in a div is itself + assertEquals("div-01", el.closest("article > div").id()); + assertEquals("article", el.closest(":not(div)").tagName()); + assertNull(el.closest("p")); + } + @Test public void elementByTagName() { Element a = new Element("P"); - assertTrue(a.tagName().equals("P")); + assertEquals("P", a.tagName()); } @Test public void testChildrenElements() { From 5d864bd94f7c301364878e54b72cfe402e46e925 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 18 Feb 2020 20:22:22 -0800 Subject: [PATCH 410/774] Issue link Fixes #1326 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0f11b429d3..79c74b5bd1 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ jsoup changelog *** Release 1.13.1 [PENDING] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. + <https://github.com/jhy/jsoup/issues/1326> * Improvement: memory optimizations, reducing the retained size of a Document by ~ 39%, and allocations by ~ 9%: 1. Attributes holder in Elements is only created if the element has attributes From 50fae5bf22955a0b34f512cac9bf77c71257c889 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 18 Feb 2020 20:42:16 -0800 Subject: [PATCH 411/774] Test for #1170 text node content No repro. --- .../java/org/jsoup/nodes/TextNodeTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index 946dffed38..df08b5674a 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.internal.StringUtil; import org.junit.Test; import java.util.List; @@ -135,4 +136,25 @@ public void testCloneAfterAttributesHit() { assertEquals("zzz", x.text()); assertEquals("xxx", y.text()); } + + @Test + public void testHasTextWhenIterating() { + // https://github.com/jhy/jsoup/issues/1170 + Document doc = Jsoup.parse("<div>One <p>Two <p>Three"); + boolean foundFirst = false; + for (Element el : doc.getAllElements()) { + for (Node node : el.childNodes()) { + if (node instanceof TextNode) { + TextNode textNode = (TextNode) node; + assertFalse(StringUtil.isBlank(textNode.text())); + if (!foundFirst) { + foundFirst = true; + assertEquals("One ", textNode.text()); + assertEquals("One ", textNode.getWholeText()); + } + } + } + } + assertTrue(foundFirst); + } } From 19be649e596b3066ed0d98d009cdd5b73d14c08d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 19 Feb 2020 21:19:03 -0800 Subject: [PATCH 412/774] Extra test for mark invalid issue --- .../java/org/jsoup/integration/ParseTest.java | 22 + src/test/resources/htmltests/xwiki-edit.html | 1015 +++++++++++++++++ 2 files changed, 1037 insertions(+) create mode 100644 src/test/resources/htmltests/xwiki-edit.html diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index e8050f3822..d145e51c66 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -3,6 +3,8 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.parser.ParseErrorList; +import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import org.junit.Test; @@ -175,6 +177,7 @@ public void testLowercaseUtf8Charset() throws IOException { @Test public void testXwiki() throws IOException { // https://github.com/jhy/jsoup/issues/1324 + // this tests that when in CharacterReader we hit a buffer while marked, we preserve the mark when buffered up and can rewind File in = getFile("/htmltests/xwiki-1324.html"); Document doc = Jsoup.parse(in, null, "https://localhost/"); assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); @@ -185,6 +188,25 @@ public void testXwiki() throws IOException { assertEquals(wantHtml, doc.select("[data-id=userdirectory]").outerHtml()); } + @Test + public void testXwikiExpanded() throws IOException { + // https://github.com/jhy/jsoup/issues/1324 + // this tests that if there is a huge illegal character reference, we can get through a buffer and rewind, and still catch that it's an invalid refence, + // and the parse tree is correct. + File in = getFile("/htmltests/xwiki-edit.html"); + Parser parser = Parser.htmlParser(); + Document doc = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://localhost/", parser.setTrackErrors(100)); + ParseErrorList errors = parser.getErrors(); + + assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); + assertEquals(0, errors.size()); // not an invalid reference because did not look legit + + // was getting busted at =userdirectory, because it hit the bufferup point but the mark was then lost. so + // updated to preserve the mark. + String wantHtml = "<a class=\"list-group-item\" data-id=\"userdirectory\" href=\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;RIGHTHERERIGHTHERERIGHTHERERIGHTHERE"; + assertTrue(doc.select("[data-id=userdirectory]").outerHtml().startsWith(wantHtml)); + } + public static File getFile(String resourceName) { try { URL resource = ParseTest.class.getResource(resourceName); diff --git a/src/test/resources/htmltests/xwiki-edit.html b/src/test/resources/htmltests/xwiki-edit.html new file mode 100644 index 0000000000..e5754fe277 --- /dev/null +++ b/src/test/resources/htmltests/xwiki-edit.html @@ -0,0 +1,1015 @@ +<!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" data-xwiki-reference="xwiki:XWiki.XWikiPreferences" data-xwiki-document="XWiki.XWikiPreferences" data-xwiki-wiki="xwiki" data-xwiki-space="XWiki" data-xwiki-page="XWikiPreferences" data-xwiki-isnew="false" data-xwiki-version="3.1" data-xwiki-rest-url="/xwiki/rest/wikis/xwiki/spaces/XWiki/pages/XWikiPreferences" data-xwiki-form-token="epgA3yUHjVWOMi0Zxgi4eQ" data-xwiki-user-reference="xwiki:XWiki.Admin" data-xwiki-locale=""> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Global Administration - XWiki</title> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="shortcut icon" href="/xwiki/resources/icons/xwiki/favicon.ico?cache-version=1581617618000" /> + <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon16.png?cache-version=1581617618000" type="image/png" /> + <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon.svg?cache-version=1581617618000" type="image/svg+xml" /> + <link rel="apple-touch-icon" href="/xwiki/resources/icons/xwiki/favicon144.png?cache-version=1581617618000" /> + <link rel="canonical" href="/xwiki/bin/view/XWiki/XWikiPreferences" /> + <meta name="revisit-after" content="7 days" /> +<meta name="description" content="Global Administration" /> +<meta name="keywords" content="wiki" /> +<meta name="rating" content="General" /> +<meta name="author" content="superadmin" /> +<link rel="alternate" type="application/rss+xml" title="Wiki Feed RSS" href="/xwiki/bin/view/Main/WebRss?xpage=rdf" /> +<link rel="alternate" type="application/rss+xml" title="Blog RSS Feed" href="/xwiki/bin/view/Blog/GlobalBlogRss?xpage=plain" /> + <link href="/xwiki/webjars/wiki%3Axwiki/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.min.css" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/xwiki-platform-tree-webjar/12.1-SNAPSHOT/tree.min.css?evaluate=true" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/selectize.js/0.12.5/css/selectize.bootstrap3.css" type='text/css' rel='stylesheet'/> + <link href="/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/css/drawer.min.css" rel="stylesheet" type="text/css" /> + + + + + +<link href=" /xwiki/bin/skin/skins/flamingo/style.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg + " rel="stylesheet" type="text/css" media="all" /> +<link href=" /xwiki/bin/skin/skins/flamingo/print.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg + " rel="stylesheet" type="text/css" media="print" /> + <!--[if IE]> + <link href=" /xwiki/bin/skin/skins/flamingo/ie-all.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg + " rel="stylesheet" type="text/css" /> +<![endif]--> + + <link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/css/xwiki-min.css?cache-version=1581618408000&colorTheme=FlamingoThemes.Iceberg&amp;language=en'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/widgets/validation/livevalidation.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/suggest/xwiki.selectize.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/js/xwiki/table/livetable.css?cache-version=1581618404000'/> + <link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Style?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Tour/HomepageTour/WebHome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Settings?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminSheet?language=en&amp;docVersion=1.1&amp;colorTheme=FlamingoThemes.Iceberg" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Panels/Applications?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Help/SupportPanel/WebHome?language=en&amp;docVersion=1.1" /> + + <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/requirejs/2.3.6/require.min.js?r=1" + data-wysiwyg="true"></script> + + <script type='text/javascript' src='/xwiki/resources/js/prototype/prototype.js?cache-version=1581618402000'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/js/scriptaculous/effects.js?cache-version=1581618404000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/xwiki-min.js?cache-version=1581618408000&defer=false&amp;language=en'></script> +<script type='text/javascript' src='/xwiki/bin/skin/skins/flamingo/flamingo.min.js?cache-version=1581618398000&language=en' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.js?cache-version=1581617618000&h=1255116547' defer='defer'></script> +<script type='text/javascript' src='/xwiki/resources/uicomponents/lock/lock.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/resources/uicomponents/widgets/validation/livevalidation_prototype.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/async/async.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/hierarchy/hierarchy.js?cache-version=1581618406000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/widgets/tree.min.js?cache-version=1581618398000' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/suggest/suggestUsersAndGroups.js?cache-version=1581618406000&language=en' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/table/livetable.js?cache-version=1581618404000' defer='defer'></script> + + <script type='text/javascript' src='/xwiki/bin/jsx/TourCode/TourJS?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Settings?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Script?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/QuickSearchUIX?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminSheet?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1' defer='defer'></script> +<script type='text/javascript' src='/xwiki/bin/jsx/Panels/Applications?language=en&amp;docVersion=1.1' defer='defer'></script> + + +<script type="text/javascript" src="/xwiki/resources/js/xwiki/compatibility.js?cache-version=1581618404000" defer="defer"></script> +<script type="text/javascript" src="/xwiki/resources/js/xwiki/markerScript.js?cache-version=1581618404000" defer="defer"></script> +<!--[if lt IE 9]> + <script src="/xwiki/webjars/wiki%3Axwiki/html5shiv/3.7.2/html5shiv.min.js?r=1"></script> + <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/respond/1.4.2/dest/respond.min.js?r=1" defer="defer"></script> +<![endif]--> +<script type="text/javascript" data-wysiwyg="true"> +// <![CDATA[ +require.config({ + paths: { + 'jquery': '/xwiki/webjars/wiki%3Axwiki/jquery/2.2.4/jquery.min.js?r=1', + 'bootstrap': '/xwiki/webjars/wiki%3Axwiki/bootstrap/3.4.1/js/bootstrap.min.js?r=1', + 'xwiki-meta': '/xwiki/resources/js/xwiki/meta.js?cache-version=1581618404000', + 'xwiki-events-bridge': "/xwiki/resources/js/xwiki/eventsBridge.js?cache-version=1581618404000", + 'iscroll': '/xwiki/webjars/wiki%3Axwiki/iscroll/5.1.3/build/iscroll-lite.js?r=1', + 'drawer': '/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/js/jquery.drawer.min.js?r=1', + 'deferred': "/xwiki/resources/uicomponents/require/deferred.js?cache-version=1581618406000" + }, + shim: { + 'bootstrap' : ['jquery'], + 'drawer': ['jquery', 'iscroll'] + }, + map: { + '*': { + 'jquery': 'jQueryNoConflict' + }, + 'jQueryNoConflict': { + 'jquery': 'jquery' + }, + } +}); +define('jQueryNoConflict', ['jquery'], function ($) { + return $.noConflict(); +}); +if (window.Prototype && Prototype.BrowserFeatures.ElementExtensions) { + require(['jquery', 'bootstrap'], function ($) { + // Fix incompatibilities between BootStrap and Prototype + var disablePrototypeJS = function (method, pluginsToDisable) { + var handler = function (event) { + event.target[method] = undefined; + setTimeout(function () { + delete event.target[method]; + }, 0); + }; + pluginsToDisable.each(function (plugin) { + $(window).on(method + '.bs.' + plugin, handler); + }); + }, + pluginsToDisable = ['collapse', 'dropdown', 'modal', 'tooltip', 'tab', 'popover']; + disablePrototypeJS('show', pluginsToDisable); + disablePrototypeJS('hide', pluginsToDisable); + }); +} +require(['jquery', 'drawer'], function($) { + $(document).ready(function() { + $('.drawer-main').closest('body').drawer(); + }); +}); +window.XWiki = window.XWiki || {}; +XWiki.webapppath = "xwiki/"; +XWiki.servletpath = "bin/"; +XWiki.contextPath = "/xwiki"; +XWiki.mainWiki = "xwiki"; +// Deprecated: replaced by meta data in the HTML element +XWiki.currentWiki = "xwiki"; +XWiki.currentSpace = "XWiki"; +XWiki.currentPage = "XWikiPreferences"; +XWiki.editor = "globaladmin"; +XWiki.viewer = ""; +XWiki.contextaction = "admin"; +XWiki.skin = 'XWiki.DefaultSkin'; +XWiki.docisnew = false; +XWiki.docsyntax = "xwiki/2.0"; +XWiki.docvariant = ""; +XWiki.blacklistedSpaces = [ ]; +XWiki.hasEdit = true; +XWiki.hasProgramming = true; +XWiki.hasBackupPackImportRights = true; +XWiki.hasRenderer = true; +window.docviewurl = "/xwiki/bin/view/XWiki/XWikiPreferences"; +window.docediturl = "/xwiki/bin/edit/XWiki/XWikiPreferences"; +window.docsaveurl = "/xwiki/bin/save/XWiki/XWikiPreferences"; +window.docgeturl = "/xwiki/bin/get/XWiki/XWikiPreferences"; +// ]]> +</script> + + <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> + <body id="body" class="skin-flamingo wiki-xwiki space-XWiki viewbody content panel-left-width-Medium panel-right-width-Medium drawer drawer-right drawer-close"> +<div id="xwikimaincontainer"> +<div id="xwikimaincontainerinner"> + + <div id="menuview"> + + + + + + + + + + + + + + + + + + + + + <nav class="navbar navbar-default actionmenu"> + <div class="container-fluid"> + <div class="navbar-header"> + <div id="companylogo"> + <a href="/xwiki/bin/view/Main/" title="Home" rel="home" class="navbar-brand"> + <img src="/xwiki/bin/download/FlamingoThemes/Iceberg/logo.svg?rev=1.1" alt="Wiki Logo"/> + </a> +</div> + + </div> + <div id="xwikimainmenu"> + + <ul class="nav navbar-nav navbar-left"> + <li class="divider" role="separator"></li> + </ul> + + <ul class="nav navbar-nav navbar-right"> + <li> + <a class="icon-navbar drawer-toggle" id="tmDrawerActivator" title="Drawer"><span class="sr-only">Toggle navigation</span><span class="fa fa-bars"></span></a> + </li> + <li class="navbar-avatar"> +<a href="/xwiki/bin/view/XWiki/Admin" class="icon-navbar"> +<span class="sr-only">User Profile</span> + <img class='avatar avatar_50' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/></a> +</li> + +<li class="dropdown" id="tmNotifications"> +<a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" +title="Notifications"> +<span class="sr-only"> +Toggle navigation +</span> +<span class="fa fa-bell"></span> +</a> +<ul class="dropdown-menu"> + <li> +<div class="notifications-header"> +<div class="clearfix"> +<div class="col-xs-4"> +<p><strong>Notifications</strong></p> +</div> +<div class="col-xs-8 text-right"> +<p> +<span class="notifications-header-link"> +<a href="/xwiki/bin/get/XWiki/Notifications/Code/NotificationRSSService?outputSyntax=plain" +class="notifications-header-link notifications-rss-link" rel="nofollow external"> +<span class="fa fa-rss"></span>&nbsp;RSS Feed +</a> +</span> +<span class="notifications-header-link"> +<a href="/xwiki/bin/view/XWiki/Admin?category=notifications" class="notifications-settings" rel="nofollow"> +<span class="fa fa-cog"></span>&nbsp;Settings +</a> +</span> +</p> +</div> +</div> +<div class="notifications-toggles clearfix" data-enabled="false"> +<pre> +<label class="hidden" for="notificationPageOnly">Toggle Page notifications only</label><input type="checkbox" id="notificationPageOnly" name="notificationPageOnly" checked="checked"/><label class="hidden" for="notificationPageAndChildren">Toggle Page and children notifications</label><input type="checkbox" id="notificationPageAndChildren" name="notificationPageAndChildren" checked="checked"/><label class="hidden" for="notificationWiki">Toggle Wiki notifications</label><input type="checkbox" id="notificationWiki" name="notificationWiki" checked="checked"/></pre> +</div> +<div class="notifications-header-uix col-xs-12"> +</div> +</div> +<div class="notifications-area loading clearfix"> +</div> +</li> + +</ul> +</li> + <li> +<form class="navbar-form globalsearch globalsearch-close form-inline" id="globalsearch" action="/xwiki/bin/view/Main/Search"> +<label class="hidden" for="headerglobalsearchinput">Search</label> +<input type="text" name="text" placeholder="search..." id="headerglobalsearchinput" /> +<button type="submit" class="btn" title="Search"><span class="fa fa-search"></span></button> +</form> +</li> + </ul> + + </div> </div> </nav> + + + + + + + +<div class="drawer-main drawer-default" id="tmDrawer"> + <nav class="drawer-nav"> + + <div class="drawer-brand clearfix"> + <a href="/xwiki/bin/view/XWiki/Admin"> + <img class='avatar avatar_120' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/> </a> + <div class="brand-links"> + <a href="/xwiki/bin/view/XWiki/Admin" class="brand-user" id="tmUser">Administrator</a> + <a href="/xwiki/bin/logout/XWiki/XWikiLogout?xredirect=%2Fxwiki%2Fbin%2Fadmin%2FXWiki%2FXWikiPreferences%3Feditor%3Dglobaladmin%26section%3DGroups" id="tmLogout" rel="nofollow"><span class="fa fa-sign-out"></span> Log-out</a> + </div> + </div> + + <ul class="drawer-menu"> + <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Home</li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/admin/XWiki/XWikiPreferences" id="tmAdminWiki" rel="nofollow"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-wrench"></span> + </div> + <div class="drawer-menu-item-text">Administer Wiki</div> + </a> + </li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/Main/AllDocs" id="tmWikiDocumentIndex"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-book"></span> + </div> + <div class="drawer-menu-item-text">Page Index</div> + </a> + </li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/Main/UserDirectory" id="tmMainUserIndex"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-user"></span> + </div> + <div class="drawer-menu-item-text">User Index</div> + </a> + </li> + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/Applications/" id="tmMainApplicationIndex"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-th"></span> + </div> + <div class="drawer-menu-item-text">Application Index</div> + </a> + </li> + <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Global</li> + + + + + + + + <li class="drawer-menu-item"> + <a href="/xwiki/bin/view/WikiManager/"> + <div class="drawer-menu-item-icon"> + <span class="fa fa-list-alt"></span> + </div> + <div class="drawer-menu-item-text">Wiki Index</div> + </a> + </li> + + </ul> + </nav> +</div> + + + + + + </div> + <div id="headerglobal"> + <div id="globallinks"> + </div> <div class="clearfloats"></div> + + + </div> + + +<div class="content" id="contentcontainer"> +<div id="contentcontainerinner"> +<div class="leftsidecolumns"> + <div id="contentcolumn"> + <div class="main layoutsubsection"> +<div id="mainContentArea"> + + + + + + + + + + + + + + + + + + + + + + + + + <ol class="breadcrumb breadcrumb-expandable" data-entities="{xwiki:XWiki.XWikiPreferences=XWiki.XWikiPreferences}" data-entity="XWiki.XWikiPreferences" data-id="hierarchy" data-limit="5" data-treenavigation="true" id="hierarchy"><li class="wiki dropdown"><a href="/xwiki/bin/view/Main/"><span class="fa fa-home"></span></a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10"></div> +</div></li><li class="space dropdown"><a href="/xwiki/bin/view/XWiki/">XWiki</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.WebHome" data-responsive="true" data-root="{&quot;type&quot;:&quot;wiki&quot;,&quot;id&quot;:&quot;xwiki&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=wiki%3Axwiki"></div> +</div></li><li class="document active dropdown"><a href="/xwiki/bin/view/XWiki/XWikiPreferences">Global Administration</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{&quot;type&quot;:&quot;document&quot;,&quot;id&quot;:&quot;xwiki:XWiki.WebHome&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=document%3Axwiki%3AXWiki.WebHome"></div> +</div> </li></ol> + <div id="document-title"><h1 id="HGlobalAdministration:Groups" class="wikigeneratedid wikigeneratedheader"><span>Global Administration: Groups</span></h1><div class="noitems"><p>Manage user groups: add or remove groups, or change group members.</p></div><hr/></div><div id="administration-menu" class="panel-group admin-menu" role="tablist" aria-multiselectable="true"> +<div class="panel xform"> +<label for="adminsearchmenu" class="hidden">Search</label> +<input type="text" class="form-control panel-group-filter" autocomplete="off" id="adminsearchmenu" +placeholder="Search for..." /> +</div> + <div class="panel panel-default"> +<a class="panel-heading" role="tab" id="panel-heading-usersgroups" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=0" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-usersgroups" aria-expanded="true" aria-controls="panel-body-usersgroups" +title="Manage users, groups, and their access rights."><span class="fa fa-group"></span>Users &#38; Rights</a> +<div class="panel-collapse collapse in" role="tabpanel" id="panel-body-usersgroups" +aria-labelledby="panel-heading-usersgroups"> +<div class="list-group"> +<a class="list-group-item" data-id="Users" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Users" title="Manage users of this wiki: add, remove, modify their profile information." +>Users</a> +<a class="list-group-item active" data-id="Groups" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Groups" title="Manage user groups: add or remove groups, or change group members." +>Groups</a> +<a class="list-group-item" data-id="Rights" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Rights" title="Manage groups and users rights: control who can view, edit and delete pages." +>Rights</a> +<a class="list-group-item" data-id="UserProfile" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=UserProfile" title="Manage what information is displayed on the user profile of each user." +>User Profile</a> +<a class="list-group-item" data-id="userdirectory" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&RIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHEREsection=userdirectory" title="Customize the user directory live table." +>User Directory</a> +<a class="list-group-item" data-id="Registration" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Registration" title="Manage user registration settings." +>Registration</a> +<a class="list-group-item" data-id="Invitation" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Invitation" title="Configure the Invitation Application" +>Invitation</a> +<a class="list-group-item" data-id="Authentication" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Authentication" title="" +>Authentication</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-extensionmanager" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=1" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-extensionmanager" aria-expanded="false" aria-controls="panel-body-extensionmanager" +title="Search, add, upgrade and remove extensions."><span class="fa fa-puzzle-piece"></span>Extensions</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-extensionmanager" +aria-labelledby="panel-heading-extensionmanager"> +<div class="list-group"> +<a class="list-group-item" data-id="XWiki.Extensions" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.Extensions" title="Search for new extensions to add to the wiki." +>Extensions</a> +<a class="list-group-item" data-id="XWiki.ExtensionHistory" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionHistory" title="See the history of the installed extensions." +>History</a> +<a class="list-group-item" data-id="XWiki.ExtensionUpdater" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionUpdater" title="Check if there are any updates available for the installed extensions." +>Updater</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-lf" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=2" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-lf" aria-expanded="false" aria-controls="panel-body-lf" +title="Change the aspect and layout of the wiki."><span class="fa fa-columns"></span>Look &#38; Feel</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-lf" +aria-labelledby="panel-heading-lf"> +<div class="list-group"> +<a class="list-group-item" data-id="Themes" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Themes" title="Customize the color and icon themes, skin and logo." +>Themes</a> +<a class="list-group-item" data-id="menu.name" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=menu.name" title="" +>Menus</a> +<a class="list-group-item" data-id="Panels.PanelWizard" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Panels.PanelWizard" title="Add and remove panels, change the page layout." +>Panels</a> +<a class="list-group-item" data-id="panels.applications" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.applications" title="Manage what applications are displayed in the Applications Panel." +>Applications Panel</a> +<a class="list-group-item" data-id="panels.navigation" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.navigation" title="Manage what pages are displayed in the Navigation Panel." +>Navigation Panel</a> +<a class="list-group-item" data-id="Presentation" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Presentation" title="Choose the page tabs that are visible and configure the page header and footer." +>Presentation</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-content" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=3" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-content" aria-expanded="false" aria-controls="panel-body-content" +title="Manipulate the content of the wiki."><span class="fa fa-file-text-o"></span>Content</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-content" +aria-labelledby="panel-heading-content"> +<div class="list-group"> +<a class="list-group-item" data-id="Templates" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Templates" title="Settings for the creation of page templates." +>Page Templates</a> +<a class="list-group-item" data-id="Localization" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Localization" title="Language-related settings." +>Localization</a> +<a class="list-group-item" data-id="Import" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Import" title="Import pages or applications into the wiki." +>Import</a> +<a class="list-group-item" data-id="Export" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Export" title="Export wiki pages into a XAR." +>Export</a> +<a class="list-group-item" data-id="Annotations" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Annotations" title="Configure the page annotations" +>Annotations</a> +<a class="list-group-item" data-id="XWiki.OfficeImporterAdmin" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.OfficeImporterAdmin" title="Configure the Office Server." +>Office Server</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-edit" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=4" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-edit" aria-expanded="false" aria-controls="panel-body-edit" +title="Configure the edit mode, the WYSIWYG editor and the available syntaxes."><span class="fa fa-pencil"></span>Editing</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-edit" +aria-labelledby="panel-heading-edit"> +<div class="list-group"> +<a class="list-group-item" data-id="Editing" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Editing" title="Choose the default edit mode and configure its title and versioning parameters." +>Edit Mode</a> +<a class="list-group-item" data-id="WYSIWYG" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=WYSIWYG" title="Choose the default WYSIWYG editor and configure it." +>WYSIWYG Editor</a> +<a class="list-group-item" data-id="Syntaxes" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Syntaxes" title="Choose the default page syntax and configure the available markup syntaxes." +>Syntaxes</a> +<a class="list-group-item" data-id="Name Strategies" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Name%20Strategies" title="" +>Name Strategies</a> +<a class="list-group-item" data-id="SyntaxHighlighting" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=SyntaxHighlighting" title="" +>Syntax Highlighting</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-email" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=5" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-email" aria-expanded="false" aria-controls="panel-body-email" +title="Configure mail sending parameters and view mail statuses."><span class="fa fa-envelope-o"></span>Mail</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-email" +aria-labelledby="panel-heading-email"> +<div class="list-group"> +<a class="list-group-item" data-id="emailSend" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailSend" title="Configure mail sending parameters." +>Mail Sending</a> +<a class="list-group-item" data-id="emailStatus" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailStatus" title="View the statuses of sent mails and resend mails that resulted in failure." +>Mail Sending Status</a> +<a class="list-group-item" data-id="emailGeneral" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailGeneral" title="Configure advanced mail parameters, like mail address obfuscation." +>Advanced</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-search" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=6" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-search" aria-expanded="false" aria-controls="panel-body-search" +title="Choose the default search engine or configure the search index."><span class="fa fa-search"></span>Search</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-search" +aria-labelledby="panel-heading-search"> +<div class="list-group"> +<a class="list-group-item" data-id="Search" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Search" title="Choose the default search engine or configure the search index." +>Search</a> +<a class="list-group-item" data-id="searchSuggest" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=searchSuggest" title="Configure the search suggest options." +>Search Suggest</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-wikis" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=7" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-wikis" aria-expanded="false" aria-controls="panel-body-wikis" +title="Wikis management."><span class="fa fa-globe"></span>Wikis</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-wikis" +aria-labelledby="panel-heading-wikis"> +<div class="list-group"> +<a class="list-group-item" data-id="wikis.descriptor" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.descriptor" title="Configure the wiki descriptor" +>Descriptor</a> +<a class="list-group-item" data-id="wikis.templates" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.templates" title="Manage the wiki templates" +>Wiki Templates</a> +<a class="list-group-item" data-id="wikis.rights" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.rights" title="" +>Creation Right</a> +</div> +</div> +</div> + <div class="panel panel-default"> +<a class="panel-heading collapsed" role="tab" id="panel-heading-other" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=8" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-other" aria-expanded="false" aria-controls="panel-body-other" +title="Various configurations for extensions."><span class="fa fa-wrench"></span>Other</a> +<div class="panel-collapse collapse" role="tabpanel" id="panel-body-other" +aria-labelledby="panel-heading-other"> +<div class="list-group"> +<a class="list-group-item" data-id="Notifications" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Notifications" title="" +>Notifications</a> +<a class="list-group-item" data-id="Logging" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Logging" title="Review and modify the log level associated to a registered logger." +>Logging</a> +<a class="list-group-item" data-id="captcha" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=captcha" title="" +>CAPTCHA</a> +<a class="list-group-item" data-id="analytics" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=analytics" title="Configure the Google Analytics™ account." +>Google Analytics™</a> +<a class="list-group-item" data-id="MessageStream" +href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=MessageStream" title="Enable or disable the message stream in the wiki." +>Message Stream</a> +</div> +</div> +</div> +<div class="panel panel-default noitems hidden"> +<div class="panel-heading collapsed"> +No results. +</div> +</div> +</div><div id="admin-page-content"> + <div class="medium-avatars"> + <div class="xwiki-livetable-container"> + <table id="groupstable" class="xwiki-livetable"> + <tr> + <td class="xwiki-livetable-pagination"> + <span id="groupstable-limits" class="xwiki-livetable-limits"></span> + <span id="groupstable-pagesize" class="xwiki-livetable-pagesize"> + <span>per page of</span> + <span class="xwiki-livetable-pagesize-content" ></span> + </span> + <span id="groupstable-ajax-loader" class="xwiki-livetable-loader hidden"> + <img src="/xwiki/resources/icons/xwiki/ajax-loader-large.gif?cache-version=1581617618000" alt="Loading..." title="" /> + </span> + <span class="controlPagination"> + <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> + <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> + </span> + <span class="pagination"> + <span class="xwiki-livetable-pagination-text">Page</span> + <span class="xwiki-livetable-pagination-content" ></span> + </span> + </td> + </tr> + <tr> + <td class="xwiki-livetable-display-container"> + <table class="xwiki-livetable-display"> + <thead class="xwiki-livetable-display-header"> + <tr> + <th scope="col" class="xwiki-livetable-display-header-text + "> + <label for="xwiki-livetable-groupstable-filter-1"> Group Name + </label> </th> + <th scope="col" class="xwiki-livetable-display-header-text + "> + Members + </th> + <th scope="col" class="xwiki-livetable-display-header-text actions + "> + Actions + </th> + </tr> + <tr class="xwiki-livetable-display-filters"> + <td class="xwiki-livetable-display-header-filter"> + <input id="xwiki-livetable-groupstable-filter-1" name="name" type="text" + title="Filter for the Group Name column" /> + </td> + <td class="xwiki-livetable-display-header-filter"> + </td> + <td class="xwiki-livetable-display-header-filter"> + </td> + </tr> + <tr class="xwiki-livetable-initial-message"> + <td colspan="3"> + <div class="warningmessage">The environment prevents the table from loading data.</div> + </td> + </tr> + </thead> + <tbody id="groupstable-display" class="xwiki-livetable-display-body"><tr><td>&nbsp;</td></tr></tbody> + </table> + </td> + </tr> + <tr> + <td class="xwiki-livetable-pagination"> + <span class="xwiki-livetable-limits"></span> + <span class="controlPagination"> + <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> + <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> + </span> + <span class="pagination"> + <span class="xwiki-livetable-pagination-text">Page</span> + <span class="xwiki-livetable-pagination-content" ></span> + </span> + </td> + </tr> + </table> + <div id="groupstable-inaccessible-docs" class="hidden"> + <div class="infomessage">(*) Some pages require special rights to be viewed.</div> + </div> + <div id="groupstable-computed-title-docs" class="hidden"> + <div class="infomessage">(<span class='docTitleComputed'></span>)&nbsp;Some pages have a computed title. Filtering and sorting by title will not work as expected for these pages.</div> + </div> + <script type="text/javascript"> + //<![CDATA[ +(function() { + var startup = function(container) { + // Make sure the LiveTable code is loaded (the WYSIWYG editor doesn't load the JavaScript code for instance). + var liveTableCodeLoaded = XWiki && XWiki.widgets && XWiki.widgets.LiveTable; + // Also make sure the live table is not already initialized. + var liveTableElement = $('groupstable'); + if (liveTableCodeLoaded && liveTableElement && !liveTableElement.__liveTable && + (liveTableElement == container || liveTableElement.descendantOf(container))) { + window["livetable_groupstable"] = liveTableElement.__liveTable = new XWiki.widgets.LiveTable("/xwiki/bin/view/XWiki/XWikiPreferences?xpage=getgroups", + "groupstable", function (row, i, table) { + // This callback method has been generated from Velocity. + var columns = ["name","members","_actions"]; + var columnDescriptors = {"name":{"type":"text","html":true,"sortable":false,"headerClass":null,"displayName":"Group Name"},"members":{"filterable":false,"sortable":false,"headerClass":null,"displayName":"Members"},"scope":{"type":"list","sortable":false,"displayName":"Scope"},"_actions":{"actions":[{"id":"edit","label":"edit","async":null,"callback":null,"icon":"<span class=\"fa fa-pencil\"></span>"},{"id":"delete","label":"delete","async":null,"callback":null,"icon":"<span class=\"fa fa-times\"></span>"}],"labels":{"delete":"delete"},"filterable":false,"headerClass":"actions","displayName":"Actions"}}; + var className = ""; + var showFilterNote = false; + if (!row['doc_viewable']) { + $("groupstable-inaccessible-docs").removeClassName('hidden'); + } + var tr = new Element('tr'); + columns.forEach(function(column) { + var descriptor = columnDescriptors[column] || {}; + if (descriptor.type === 'hidden') { + return; + } + // The column's display name to be used when displaying the reponsive version. + var displayName = descriptor.displayName || column; + var fieldName = column.replace(/^doc\./, 'doc_'); + if (column === '_actions') { + var adminActions = ['admin', 'rename', 'rights']; + var td = new Element('td', { + 'class': 'actions', + 'data-title': displayName + }); + td.toggleClassName('hide-labels', descriptor.labels === false); + (descriptor.actions || []).forEach(function(action, index) { + if (row['doc_has' + action.id] || action.id === 'view' || (row['doc_has' + action.id] === undefined && + (row['doc_hasadmin'] || adminActions.indexOf(action.id) < 0))) { + var link = new Element('a', { + 'href': row['doc_' + action.id + '_url'] || row['doc_url'], + 'class': 'action action' + action.id + }).update('<span class="action-icon"></span><span class="action-label"></span>'); + link.down('.action-icon').update(action.icon).writeAttribute('title', action.label); + link.down('.action-label').update(action.label.escapeHTML()); + if (action.async) { + link.observe('click', function(event) { + event.stop(); + new Ajax.Request(this.href, { + onSuccess: function() { + eval(action.callback); + } + }); + }.bindAsEventListener(link)); + } + td.insert(link); + } + }); + tr.appendChild(td); + } else { + var td = new Element('td', { + 'class': [ + fieldName, + 'link' + (descriptor.link || ''), + 'type' + (descriptor.type || '') + ].join(' '), + 'data-title': displayName + }); + var container = td; + if (descriptor.link && row['doc_viewable']) { + var link = new Element(descriptor.link === 'editor' ? 'span' : 'a'); + // Automatic: the link URL is in JSON results, with the '_url' sufix. + if (descriptor.link === 'auto') { + link.href = row[fieldName + '_url'] || row['doc_url']; + } else if (descriptor.link === 'field') { + if (row[fieldName + '_url']) { + link.href = row[fieldName + '_url']; + } + // Property editor + } else if (descriptor.link === 'editor') { + var propertyClassName = descriptor['class'] || className; + td.observe('click', function(event) { + var tag = event.element().down('span') || event.element(); + editProperty(row['doc_fullName'], propertyClassName, column, function(value) { + tag.innerHTML = value; + }); + }); + // Author, space or wiki link. + } else if (row['doc_' + descriptor.link + '_url']) { + link.href = row['doc_' + descriptor.link + '_url']; + } else { + link.href = row['doc_url']; + } + td.appendChild(link); + container = link; + } + // The value can be passed as a string.. + if (descriptor.html + '' === 'true') { + container.innerHTML = row[fieldName] || ''; + } else if (row[fieldName] !== undefined && row[fieldName] !== null) { + var text = row[fieldName] + ''; + if (fieldName === 'doc_name' && !row['doc_viewable']) { + text += '*'; + } + if (showFilterNote && fieldName === 'doc_title' && row['doc_title_raw'] !== undefined) { + container.addClassName('docTitleComputed'); + } + container.update(text.escapeHTML()); + } + tr.appendChild(td); + } + }); + return tr; +} +, {"maxPages":10,"limit":15,"selectedTags":[]}); + document.observe("xwiki:livetable:groupstable:loadingEntries", function() { + $('groupstable-pagesize').addClassName("hidden"); + }); + document.observe("xwiki:livetable:groupstable:loadingComplete", function() { + $('groupstable-pagesize').removeClassName("hidden"); + }); + return true; + } + return false; + }; + var init = function(event) { + var elements = (event && event.memo.elements) || [$('body')]; + return elements.length > 0 && elements.some(startup); + }; + // Initialize the live table on page load or after the live table code has been loaded. + (XWiki && XWiki.isInitialized && init()) || document.observe('xwiki:livetable:loading', init); + // Initialize the live table when it is added after the page is loaded. + document.observe('xwiki:dom:updated', init); +})(); + //]]> + </script> +</div></div> +<p> +<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#createGroupModal"> +Create group +</button> +</p> +<div class="modal" id="createGroupModal" tabindex="-1" role="dialog" +aria-labelledby="createGroupModal-label" data-backdrop="static" data-keyboard="false"> +<div class="modal-dialog" role="document"> +<form class="modal-content xform"> +<div class="modal-header"> +<button type="button" class="close" data-dismiss="modal" aria-label="Close"> +<span aria-hidden="true">&times;</span> +</button> +<div class="modal-title" id="createGroupModal-label"> +Create group +</div> +</div> +<div class="modal-body"> +<div class="hidden"> +<input type="hidden" name="form_token" value="epgA3yUHjVWOMi0Zxgi4eQ" /> +<input type="hidden" name="template" value="XWiki.XWikiGroupTemplate" /> +</div> +<dl> +<dt> +<label for="createGroupModal-groupName" class="sr-only"> +Group Name +</label> +</dt> +<dd class="form-group has-feedback"> +<input type="text" class="form-control" id="createGroupModal-groupName" name="name" autocomplete="off" +placeholder="Group Name" /> +<span class="form-control-feedback loading hidden" aria-hidden="true"></span> +<span class="form-control-feedback success hidden" aria-hidden="true"><span class="fa fa-check"></span></span> +<span class="form-control-feedback error hidden" aria-hidden="true"><span class="fa fa-times"></span></span> +<span class="help-block hidden"></span> +</dd> +</dl> +</div> +<div class="modal-footer"> +<button type="button" class="btn btn-default" data-dismiss="modal"> +Cancel +</button> +<button type="submit" class="btn btn-primary"> +Create +</button> +</div> +</form> +</div> +</div> + +<div class="modal" id="editGroupModal" tabindex="-1" role="dialog" aria-labelledby="editGroupModal-label" +data-backdrop="static" data-keyboard="false" data-liveTable="#groupstable" data-liveTableAction="edit"> +<div class="modal-dialog" role="document"> +<div class="modal-content"> +<div class="modal-header"> +<button type="button" class="close" data-dismiss="modal" aria-label="Close"> +<span aria-hidden="true">&times;</span> +</button> +<div class="modal-title" id="editGroupModal-label"> +Edit group +</div> +</div> +<div class="modal-body"></div> +</div> +</div> +</div> + +<div class="modal" id="deleteGroupModal" tabindex="-1" role="dialog" aria-labelledby="deleteGroupModal-label" +data-liveTable="#groupstable" data-liveTableAction="delete"> +<div class="modal-dialog" role="document"> +<div class="modal-content"> +<div class="modal-header"> +<button type="button" class="close" data-dismiss="modal" aria-label="Close"> +<span aria-hidden="true">&times;</span> +</button> +<div class="modal-title" id="deleteGroupModal-label"> +Delete group +</div> +</div> +<div class="modal-body"> +<p>The group <span class="groupName"></span> will be deleted. Are you sure you want to proceed?</p> +</div> +<div class="modal-footer"> +<button type="button" class="btn btn-default" data-dismiss="modal"> +Cancel +</button> +<button type="submit" class="btn btn-danger" data-dismiss="modal"> +Delete +</button> +</div> +</div> +</div> +</div> +</div> + <div class="clearfloats"></div> +</div></div></div><div id="leftPanels" class="panels left panel-width-Medium"> + <div class="panel expanded PanelsApplications Applications"><h1 class="xwikipaneltitle">Applications</h1><div class="xwikipanelcontents"><ul class="applicationsPanel nav nav-pills nav-stacked"> +<li> +<a href="/xwiki/bin/view/Dashboard/" title="Dashboard"> +<span class="application-img"><span class="fa fa-th-large"></span></span> +<span class="application-label">Dashboard</span> +</a> +</li> +<li> +<a href="/xwiki/bin/view/Help/" title="Help"> +<span class="application-img"><span class="fa fa-question-circle"></span></span> +<span class="application-label">Help</span> +</a> +</li> +<li> +<a href="/xwiki/bin/view/Sandbox/" title="Sandbox"> +<span class="application-img"><span class="fa fa-coffee"></span></span> +<span class="application-label">Sandbox</span> +</a> +</li> +</ul> +<ul class="applicationsPanel applicationsPanelMoreList nav nav-pills nav-stacked"> +<li> +<a class="applicationPanelMoreButton" href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.AddExtensions" title="More applications"> +<span class="application-img"><span class="fa fa-plus"></span></span> +<span class="application-label">More applications</span> +</a> +<div class="applicationPanelMoreContainer hidden"> +<ul class="nav nav-pills nav-stacked"> +<li> +<a href="/xwiki/bin/view/AppWithinMinutes/" title="Create your own!"> +<span class="application-img"><span class="fa fa-caret-right"></span></span> +<span class="application-label">Create your own!</span> +</a> +</li> +<li> +<a href="/xwiki/bin/view/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.Extensions" title="Install new applications"> +<span class="application-img"><span class="fa fa-caret-right"></span></span> +<span class="application-label">Install new applications</span> +</a> +</li> +</ul> +</div> +</li> +</ul></div></div> + <div class="panel expanded PanelsNavigation Navigation"><h1 class="xwikipaneltitle">Navigation</h1><div class="xwikipanelcontents"> + + + + + + + + <div class = "xtree" data-responsive = "true" data-url = "/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&#38;sheet=XWiki.DocumentTree&#38;showAttachments=false&#38;showTranslations=false&#38;exclusions=document%3Axwiki%3ASandbox.WebHome&#38;exclusions=document%3Axwiki%3AHelp.WebHome&#38;exclusions=document%3Axwiki%3AMenu.WebHome&#38;exclusions=document%3Axwiki%3AXWiki.WebHome" data-dragAndDrop = "false" data-contextMenu = "false" data-icons = "false" data-edges = "false" data-checkboxes = "false" data-openTo = "document:xwiki:XWiki.XWikiPreferences" data-finder = "false" ></div></div></div> + </div> + + </div><div id="rightPanels" class="panels right panel-width-Medium"> + <div class="xwiki-async" data-xwiki-async-id="uix/xwiki%3AHelp.TipsPanel.WebHome/author/xwiki%3AXWiki.superadmin/locale/en/secureDocument/xwiki%3AHelp.TipsPanel.WebHome/9" data-xwiki-async-client-id="9"></div> + + <div class="panel expanded HelpSupportPanel WebHome"><h1 class="xwikipaneltitle">Need help?</h1><div class="xwikipanelcontents"><div class="SupportPanel"><p>If you need help with XWiki you can contact:</p><ul><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HCommunitySupport">Community Support</a></span></li><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HProfessionalSupport">Professional Support</a></span></li></ul></div></div></div> + </div> + +<div class="clearfloats"></div> + </div></div><div id="footerglobal"> + <div id="xwikilicence"></div> + <div id="xwikiplatformversion"> + <a href="http://extensions.xwiki.org?id=org.xwiki.platform:xwiki-platform-distribution-jetty-hsqldb:12.1-SNAPSHOT:::/xwiki-commons-pom/xwiki-platform/xwiki-platform-distribution/xwiki-platform-distribution-jetty-hsqldb"> + XWiki Jetty HSQLDB 12.1-SNAPSHOT + </a> + </div> + </div> + +</div></div></body> +</html> \ No newline at end of file From d9dfc4a6f2769a7211113b54427b5fd8966f418a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 19 Feb 2020 21:41:37 -0800 Subject: [PATCH 413/774] Moved some longer running tests to IT tests And allow longer runtime for slower CPUs --- .../java/org/jsoup/integration/ParseTest.java | 24 ++++++ src/test/java/org/jsoup/nodes/ElementIT.java | 79 +++++++++++++++++++ .../java/org/jsoup/nodes/ElementTest.java | 68 ---------------- .../java/org/jsoup/parser/HtmlParserTest.java | 23 ------ src/test/java/org/jsoup/parser/ParserIT.java | 29 +++++++ 5 files changed, 132 insertions(+), 91 deletions(-) create mode 100644 src/test/java/org/jsoup/nodes/ElementIT.java diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index d145e51c66..cfdd059947 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -12,6 +12,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import static org.junit.Assert.*; @@ -207,6 +208,24 @@ public void testXwikiExpanded() throws IOException { assertTrue(doc.select("[data-id=userdirectory]").outerHtml().startsWith(wantHtml)); } + @Test public void testWikiExpandedFromString() throws IOException { + File in = getFile("/htmltests/xwiki-edit.html"); + String html = getFileAsString(in); + Document doc = Jsoup.parse(html); + assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); + String wantHtml = "<a class=\"list-group-item\" data-id=\"userdirectory\" href=\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;RIGHTHERERIGHTHERERIGHTHERERIGHTHERE"; + assertTrue(doc.select("[data-id=userdirectory]").outerHtml().startsWith(wantHtml)); + } + + @Test public void testWikiFromString() throws IOException { + File in = getFile("/htmltests/xwiki-1324.html"); + String html = getFileAsString(in); + Document doc = Jsoup.parse(html); + assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); + String wantHtml = "<a class=\"list-group-item\" data-id=\"userdirectory\" href=\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=userdirectory\" title=\"Customize the user directory live table.\">User Directory</a>"; + assertEquals(wantHtml, doc.select("[data-id=userdirectory]").outerHtml()); + } + public static File getFile(String resourceName) { try { URL resource = ParseTest.class.getResource(resourceName); @@ -220,4 +239,9 @@ public static InputStream inputStreamFrom(String s) { return new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)); } + public static String getFileAsString(File file) throws IOException { + byte[] bytes = Files.readAllBytes(file.toPath()); + return new String(bytes); + } + } diff --git a/src/test/java/org/jsoup/nodes/ElementIT.java b/src/test/java/org/jsoup/nodes/ElementIT.java new file mode 100644 index 0000000000..bbd022c8a5 --- /dev/null +++ b/src/test/java/org/jsoup/nodes/ElementIT.java @@ -0,0 +1,79 @@ +package org.jsoup.nodes; + +import org.jsoup.Jsoup; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ElementIT { + @Test + public void testFastReparent() { + StringBuilder htmlBuf = new StringBuilder(); + int rows = 300000; + for (int i = 1; i <= rows; i++) { + htmlBuf + .append("<p>El-") + .append(i) + .append("</p>"); + } + String html = htmlBuf.toString(); + Document doc = Jsoup.parse(html); + long start = System.currentTimeMillis(); + + Element wrapper = new Element("div"); + List<Node> childNodes = doc.body().childNodes(); + wrapper.insertChildren(0, childNodes); + + long runtime = System.currentTimeMillis() - start; + assertEquals(rows, wrapper.childNodes.size()); + assertEquals(0, childNodes.size()); // all moved out + + doc.body().empty().appendChild(wrapper); + Element wrapperAcutal = doc.body().children().get(0); + assertEquals(wrapper, wrapperAcutal); + assertEquals("El-1", wrapperAcutal.children().get(0).text()); + assertEquals("El-" + rows, wrapperAcutal.children().get(rows - 1).text()); + assertTrue(runtime <= 10000); + } + + @Test + public void testFastReparentExistingContent() { + StringBuilder htmlBuf = new StringBuilder(); + int rows = 300000; + for (int i = 1; i <= rows; i++) { + htmlBuf + .append("<p>El-") + .append(i) + .append("</p>"); + } + String html = htmlBuf.toString(); + Document doc = Jsoup.parse(html); + long start = System.currentTimeMillis(); + + Element wrapper = new Element("div"); + wrapper.append("<p>Prior Content</p>"); + wrapper.append("<p>End Content</p>"); + assertEquals(2, wrapper.childNodes.size()); + + List<Node> childNodes = doc.body().childNodes(); + wrapper.insertChildren(1, childNodes); + + long runtime = System.currentTimeMillis() - start; + assertEquals(rows + 2, wrapper.childNodes.size()); + assertEquals(0, childNodes.size()); // all moved out + + doc.body().empty().appendChild(wrapper); + Element wrapperAcutal = doc.body().children().get(0); + assertEquals(wrapper, wrapperAcutal); + assertEquals("Prior Content", wrapperAcutal.children().get(0).text()); + assertEquals("El-1", wrapperAcutal.children().get(1).text()); + + assertEquals("El-" + rows, wrapperAcutal.children().get(rows).text()); + assertEquals("End Content", wrapperAcutal.children().get(rows + 1).text()); + + assertTrue(runtime <= 10000); + } +} diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 5b1877f31f..3b833a3ca8 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1574,74 +1574,6 @@ public void doesntDeleteZWJWhenNormalizingText() { } - @Test - public void testFastReparent() { - StringBuilder htmlBuf = new StringBuilder(); - int rows = 300000; - for (int i = 1; i <= rows; i++) { - htmlBuf - .append("<p>El-") - .append(i) - .append("</p>"); - } - String html = htmlBuf.toString(); - Document doc = Jsoup.parse(html); - long start = System.currentTimeMillis(); - - Element wrapper = new Element("div"); - List<Node> childNodes = doc.body().childNodes(); - wrapper.insertChildren(0, childNodes); - - long runtime = System.currentTimeMillis() - start; - assertEquals(rows, wrapper.childNodes.size()); - assertEquals(0, childNodes.size()); // all moved out - - doc.body().empty().appendChild(wrapper); - Element wrapperAcutal = doc.body().children().get(0); - assertEquals(wrapper, wrapperAcutal); - assertEquals("El-1", wrapperAcutal.children().get(0).text()); - assertEquals("El-" + rows, wrapperAcutal.children().get(rows - 1).text()); - assertTrue(runtime <= 1000); - } - - @Test - public void testFastReparentExistingContent() { - StringBuilder htmlBuf = new StringBuilder(); - int rows = 300000; - for (int i = 1; i <= rows; i++) { - htmlBuf - .append("<p>El-") - .append(i) - .append("</p>"); - } - String html = htmlBuf.toString(); - Document doc = Jsoup.parse(html); - long start = System.currentTimeMillis(); - - Element wrapper = new Element("div"); - wrapper.append("<p>Prior Content</p>"); - wrapper.append("<p>End Content</p>"); - assertEquals(2, wrapper.childNodes.size()); - - List<Node> childNodes = doc.body().childNodes(); - wrapper.insertChildren(1, childNodes); - - long runtime = System.currentTimeMillis() - start; - assertEquals(rows + 2, wrapper.childNodes.size()); - assertEquals(0, childNodes.size()); // all moved out - - doc.body().empty().appendChild(wrapper); - Element wrapperAcutal = doc.body().children().get(0); - assertEquals(wrapper, wrapperAcutal); - assertEquals("Prior Content", wrapperAcutal.children().get(0).text()); - assertEquals("El-1", wrapperAcutal.children().get(1).text()); - - assertEquals("El-" + rows, wrapperAcutal.children().get(rows).text()); - assertEquals("End Content", wrapperAcutal.children().get(rows + 1).text()); - - assertTrue(runtime <= 1000); - } - @Test public void testReparentSeperateNodes() { String html = "<div><p>One<p>Two"; diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index d41144ca28..c93bcd8fdc 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -992,29 +992,6 @@ public class HtmlParserTest { assertTrue(System.currentTimeMillis() - start < 1000); } - @Test public void handlesDeepStack() { - // inspired by http://sv.stargate.wikia.com/wiki/M2J and https://github.com/jhy/jsoup/issues/955 - // I didn't put it in the integration tests, because explorer and intellij kept dieing trying to preview/index it - - // Arrange - StringBuilder longBody = new StringBuilder(500000); - for (int i = 0; i < 25000; i++) { - longBody.append(i).append("<dl><dd>"); - } - for (int i = 0; i < 25000; i++) { - longBody.append(i).append("</dd></dl>"); - } - - // Act - long start = System.currentTimeMillis(); - Document doc = Parser.parseBodyFragment(longBody.toString(), ""); - - // Assert - assertEquals(2, doc.body().childNodeSize()); - assertEquals(25000, doc.select("dd").size()); - assertTrue(System.currentTimeMillis() - start < 2000); - } - @Test public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/table-invalid-elements.html"); diff --git a/src/test/java/org/jsoup/parser/ParserIT.java b/src/test/java/org/jsoup/parser/ParserIT.java index ca0589d3c7..a4c5173f1a 100644 --- a/src/test/java/org/jsoup/parser/ParserIT.java +++ b/src/test/java/org/jsoup/parser/ParserIT.java @@ -1,7 +1,11 @@ package org.jsoup.parser; +import org.jsoup.nodes.Document; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * Longer running Parser tests. */ @@ -20,4 +24,29 @@ public void testIssue1251() { str.insert(countSpaces, ' '); } } + + @Test + public void handlesDeepStack() { + // inspired by http://sv.stargate.wikia.com/wiki/M2J and https://github.com/jhy/jsoup/issues/955 + // I didn't put it in the integration tests, because explorer and intellij kept dieing trying to preview/index it + + // Arrange + StringBuilder longBody = new StringBuilder(500000); + for (int i = 0; i < 25000; i++) { + longBody.append(i).append("<dl><dd>"); + } + for (int i = 0; i < 25000; i++) { + longBody.append(i).append("</dd></dl>"); + } + + // Act + long start = System.currentTimeMillis(); + Document doc = Parser.parseBodyFragment(longBody.toString(), ""); + + // Assert + assertEquals(2, doc.body().childNodeSize()); + assertEquals(25000, doc.select("dd").size()); + assertTrue(System.currentTimeMillis() - start < 20000); // I get ~ 1.5 seconds, but others have reported slower + // was originally much longer, or stack overflow. + } } From a657ae0240b875a703a1e4909d56ad59997d362d Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sat, 22 Feb 2020 10:37:27 -0800 Subject: [PATCH 414/774] Added forms(), comments(), textNodes(), dataNodes() --- CHANGES | 3 + src/main/java/org/jsoup/select/Elements.java | 52 +++++++++++++++-- .../java/org/jsoup/select/ElementsTest.java | 56 ++++++++++++++++++- 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 79c74b5bd1..ac44b86c97 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,9 @@ jsoup changelog selector if using the same evaluator many times. <https://github.com/jhy/jsoup/issues/1319> + * Improvement: added Elements#forms(), Elements#textNodes(), Elements#dataNodes(), and Elements#comments(), as a + convenient way to get access to these node types directly from an element selection. + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 1a205f909c..7689021f4d 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -1,10 +1,13 @@ package org.jsoup.select; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.DataNode; import org.jsoup.nodes.Element; import org.jsoup.nodes.FormElement; import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import java.util.ArrayList; import java.util.Arrays; @@ -633,11 +636,48 @@ public Elements filter(NodeFilter nodeFilter) { * no forms. */ public List<FormElement> forms() { - ArrayList<FormElement> forms = new ArrayList<>(); - for (Element el: this) - if (el instanceof FormElement) - forms.add((FormElement) el); - return forms; + return nodesOfType(FormElement.class); + } + + /** + * Get {@link Comment} nodes that are direct child nodes of the selected elements. + * @return Comment nodes, or an empty list if none. + */ + public List<Comment> comments() { + return nodesOfType(Comment.class); + } + + /** + * Get {@link TextNode} nodes that are direct child nodes of the selected elements. + * @return TextNode nodes, or an empty list if none. + */ + public List<TextNode> textNodes() { + return nodesOfType(TextNode.class); + } + + /** + * Get {@link DataNode} nodes that are direct child nodes of the selected elements. DataNode nodes contain the + * content of tags such as {@code script}, {@code style} etc and are distinct from {@link TextNode}s. + * @return Comment nodes, or an empty list if none. + */ + public List<DataNode> dataNodes() { + return nodesOfType(DataNode.class); + } + + private <T extends Node> List<T> nodesOfType(Class<T> tClass) { + ArrayList<T> nodes = new ArrayList<>(); + for (Element el: this) { + if (el.getClass().isInstance(tClass)) { // Handles FormElements + nodes.add(tClass.cast(el)); + } else if (Node.class.isAssignableFrom(tClass)) { // check if child nodes match + for (int i = 0; i < el.childNodeSize(); i++) { + Node node = el.childNode(i); + if (tClass.isInstance(node)) + nodes.add(tClass.cast(node)); + } + } + } + return nodes; } } diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index d54c4d0798..a09c98d704 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -2,17 +2,18 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.DataNode; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.FormElement; import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import org.junit.Test; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** Tests for ElementList. @@ -298,6 +299,55 @@ public void tail(Node node, int depth) { assertEquals("2", forms.get(1).id()); } + @Test public void comments() { + Document doc = Jsoup.parse("<!-- comment1 --><p><!-- comment2 --><p class=two><!-- comment3 -->"); + List<Comment> comments = doc.select("p").comments(); + assertEquals(2, comments.size()); + assertEquals(" comment2 ", comments.get(0).getData()); + assertEquals(" comment3 ", comments.get(1).getData()); + + List<Comment> comments1 = doc.select("p.two").comments(); + assertEquals(1, comments1.size()); + assertEquals(" comment3 ", comments1.get(0).getData()); + } + + @Test public void textNodes() { + Document doc = Jsoup.parse("One<p>Two<a>Three</a><p>Four</p>Five"); + List<TextNode> textNodes = doc.select("p").textNodes(); + assertEquals(2, textNodes.size()); + assertEquals("Two", textNodes.get(0).text()); + assertEquals("Four", textNodes.get(1).text()); + } + + @Test public void dataNodes() { + Document doc = Jsoup.parse("<p>One</p><script>Two</script><style>Three</style>"); + List<DataNode> dataNodes = doc.select("p, script, style").dataNodes(); + assertEquals(2, dataNodes.size()); + assertEquals("Two", dataNodes.get(0).getWholeData()); + assertEquals("Three", dataNodes.get(1).getWholeData()); + + doc = Jsoup.parse("<head><script type=application/json><crux></script><script src=foo>Blah</script>"); + Elements script = doc.select("script[type=application/json]"); + List<DataNode> scriptNode = script.dataNodes(); + assertEquals(1, scriptNode.size()); + DataNode dataNode = scriptNode.get(0); + assertEquals("<crux>", dataNode.getWholeData()); + + // check if they're live + dataNode.setWholeData("<cromulent>"); + assertEquals("<script type=\"application/json\"><cromulent></script>", script.outerHtml()); + } + + @Test public void nodesEmpty() { + Document doc = Jsoup.parse("<p>"); + assertEquals(0, doc.select("form").textNodes().size()); + } + + @Test public void formElementsDescendButNotAccumulate() { + Document doc = Jsoup.parse("<div><div><form id=1>"); + assertEquals(1, doc.select("div").forms().size()); + } + @Test public void classWithHyphen() { Document doc = Jsoup.parse("<p class='tab-nav'>Check</p>"); Elements els = doc.getElementsByClass("tab-nav"); From 9d9e53c8f5ab7d5dfa689ff39ee9e5dc5f7a881e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 22 Feb 2020 22:39:26 -0800 Subject: [PATCH 415/774] Fixed a performance regression in 1.12 Was building useless stringbuilders on every token read! --- src/main/java/org/jsoup/parser/Tokeniser.java | 19 +++++++++---------- .../java/org/jsoup/parser/TreeBuilder.java | 7 ++++++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index c5e4bc203f..19fb52a445 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -53,21 +53,20 @@ final class Tokeniser { } Token read() { - int pos = reader.pos(); // count how many reads we do in a row without making progress, and bail if stuck in a loop + final CharacterReader r = this.reader; + final int pos = r.pos(); // count how many reads we do in a row without making progress, and bail if stuck in a loop int consecutiveReads = 0; while (!isEmitPending) { - state.read(this, reader); - if (reader.pos() <= pos) { - consecutiveReads++; - } - Validate.isTrue(consecutiveReads < 10, - "BUG: Not making progress from state: " + this.state.name() + " with current char=" + reader.current()); + state.read(this, r); + if (++consecutiveReads > 10 && r.pos() <= pos) + Validate.wtf("BUG: Not making progress from state: " + this.state.name() + " with current char=" + r.current()); } // if emit is pending, a non-character token was found: return any chars in buffer, and leave token for next read: - if (charsBuilder.length() > 0) { - String str = charsBuilder.toString(); - charsBuilder.delete(0, charsBuilder.length()); + final StringBuilder cb = this.charsBuilder; + if (cb.length() != 0) { + String str = cb.toString(); + cb.delete(0, cb.length()); charsString = null; return charPending.data(str); } else if (charsString != null) { diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 00c4d95ac8..5292705c17 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -58,12 +58,15 @@ Document parse(Reader input, String baseUri, Parser parser) { abstract List<Node> parseFragment(String inputFragment, Element context, String baseUri, Parser parser); protected void runParser() { + final Tokeniser tokeniser = this.tokeniser; + final Token.TokenType eof = Token.TokenType.EOF; + while (true) { Token token = tokeniser.read(); process(token); token.reset(); - if (token.type == Token.TokenType.EOF) + if (token.type == eof) break; } } @@ -71,6 +74,7 @@ protected void runParser() { protected abstract boolean process(Token token); protected boolean processStartTag(String name) { + final Token.StartTag start = this.start; if (currentToken == start) { // don't recycle an in-use token return process(new Token.StartTag().name(name)); } @@ -78,6 +82,7 @@ protected boolean processStartTag(String name) { } public boolean processStartTag(String name, Attributes attrs) { + final Token.StartTag start = this.start; if (currentToken == start) { // don't recycle an in-use token return process(new Token.StartTag().nameAttr(name, attrs)); } From c693d6e42a06c09fca13dab8bd2c8d465481badb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 22 Feb 2020 23:43:21 -0800 Subject: [PATCH 416/774] Use switch for start, end tags --- .../jsoup/parser/HtmlTreeBuilderState.java | 991 ++++++++++-------- 1 file changed, 526 insertions(+), 465 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 05c28ccd47..20dc31f65e 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -250,6 +250,9 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { }, InBody { boolean process(Token t, HtmlTreeBuilder tb) { + ArrayList<Element> stack; + Element el; + switch (t.type) { case Character: { Token.Character c = t.asCharacter(); @@ -277,485 +280,546 @@ boolean process(Token t, HtmlTreeBuilder tb) { } case StartTag: Token.StartTag startTag = t.asStartTag(); - // todo - refactor to a switch statement String name = startTag.normalName(); - if (name.equals("a")) { - if (tb.getActiveFormattingElement("a") != null) { - tb.error(this); - tb.processEndTag("a"); - // still on stack? - Element remainingA = tb.getFromStack("a"); - if (remainingA != null) { - tb.removeFromActiveFormattingElements(remainingA); - tb.removeFromStack(remainingA); - } - } - tb.reconstructFormattingElements(); - Element a = tb.insert(startTag); - tb.pushActiveFormattingElements(a); - } else if (StringUtil.inSorted(name, Constants.InBodyStartEmptyFormatters)) { - tb.reconstructFormattingElements(); - tb.insertEmpty(startTag); - tb.framesetOk(false); - } else if (StringUtil.inSorted(name, Constants.InBodyStartPClosers)) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - } else if (name.equals("span")) { - // same as final else, but short circuits lots of checks - tb.reconstructFormattingElements(); - tb.insert(startTag); - } else if (name.equals("li")) { - tb.framesetOk(false); - ArrayList<Element> stack = tb.getStack(); - for (int i = stack.size() - 1; i > 0; i--) { - Element el = stack.get(i); - if (el.normalName().equals("li")) { - tb.processEndTag("li"); - break; + switch (name) { + case "a": + if (tb.getActiveFormattingElement("a") != null) { + tb.error(this); + tb.processEndTag("a"); + + // still on stack? + Element remainingA = tb.getFromStack("a"); + if (remainingA != null) { + tb.removeFromActiveFormattingElements(remainingA); + tb.removeFromStack(remainingA); + } } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) - break; - } - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - } else if (name.equals("html")) { - tb.error(this); - // merge attributes onto real html - Element html = tb.getStack().get(0); - for (Attribute attribute : startTag.getAttributes()) { - if (!html.hasAttr(attribute.getKey())) - html.attributes().put(attribute); - } - } else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { - return tb.process(t, InHead); - } else if (name.equals("body")) { - tb.error(this); - ArrayList<Element> stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { - // only in fragment case - return false; // ignore - } else { + tb.reconstructFormattingElements(); + Element a = tb.insert(startTag); + tb.pushActiveFormattingElements(a); + break; + case "span": + // same as final else, but short circuits lots of checks + tb.reconstructFormattingElements(); + tb.insert(startTag); + break; + case "li": tb.framesetOk(false); - Element body = stack.get(1); - for (Attribute attribute : startTag.getAttributes()) { - if (!body.hasAttr(attribute.getKey())) - body.attributes().put(attribute); + stack = tb.getStack(); + for (int i = stack.size() - 1; i > 0; i--) { + el = stack.get(i); + if (el.normalName().equals("li")) { + tb.processEndTag("li"); + break; + } + if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) + break; + } + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); } - } - } else if (name.equals("frameset")) { - tb.error(this); - ArrayList<Element> stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { - // only in fragment case - return false; // ignore - } else if (!tb.framesetOk()) { - return false; // ignore frameset - } else { - Element second = stack.get(1); - if (second.parent() != null) - second.remove(); - // pop up to html element - while (stack.size() > 1) - stack.remove(stack.size()-1); tb.insert(startTag); - tb.transition(InFrameset); - } - } else if (StringUtil.inSorted(name, Constants.Headings)) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { + break; + case "html": tb.error(this); - tb.pop(); - } - tb.insert(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartPreListing)) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - tb.reader.matchConsume("\n"); // ignore LF if next token - tb.framesetOk(false); - } else if (name.equals("form")) { - if (tb.getFormElement() != null) { + // merge attributes onto real html + Element html = tb.getStack().get(0); + for (Attribute attribute : startTag.getAttributes()) { + if (!html.hasAttr(attribute.getKey())) + html.attributes().put(attribute); + } + break; + case "body": tb.error(this); - return false; - } - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insertForm(startTag, true); - } else if (StringUtil.inSorted(name, Constants.DdDt)) { - tb.framesetOk(false); - ArrayList<Element> stack = tb.getStack(); - for (int i = stack.size() - 1; i > 0; i--) { - Element el = stack.get(i); - if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { - tb.processEndTag(el.normalName()); - break; + stack = tb.getStack(); + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { + // only in fragment case + return false; // ignore + } else { + tb.framesetOk(false); + Element body = stack.get(1); + for (Attribute attribute : startTag.getAttributes()) { + if (!body.hasAttr(attribute.getKey())) + body.attributes().put(attribute); + } } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) - break; - } - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - } else if (name.equals("plaintext")) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - tb.tokeniser.transition(TokeniserState.PLAINTEXT); // once in, never gets out - } else if (name.equals("button")) { - if (tb.inButtonScope("button")) { - // close and reprocess + break; + case "frameset": tb.error(this); - tb.processEndTag("button"); - tb.process(startTag); - } else { + stack = tb.getStack(); + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { + // only in fragment case + return false; // ignore + } else if (!tb.framesetOk()) { + return false; // ignore frameset + } else { + Element second = stack.get(1); + if (second.parent() != null) + second.remove(); + // pop up to html element + while (stack.size() > 1) + stack.remove(stack.size() - 1); + tb.insert(startTag); + tb.transition(InFrameset); + } + break; + case "form": + if (tb.getFormElement() != null) { + tb.error(this); + return false; + } + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insertForm(startTag, true); + break; + case "plaintext": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + tb.tokeniser.transition(TokeniserState.PLAINTEXT); // once in, never gets out + break; + case "button": + if (tb.inButtonScope("button")) { + // close and reprocess + tb.error(this); + tb.processEndTag("button"); + tb.process(startTag); + } else { + tb.reconstructFormattingElements(); + tb.insert(startTag); + tb.framesetOk(false); + } + break; + case "nobr": tb.reconstructFormattingElements(); + if (tb.inScope("nobr")) { + tb.error(this); + tb.processEndTag("nobr"); + tb.reconstructFormattingElements(); + } + el = tb.insert(startTag); + tb.pushActiveFormattingElements(el); + break; + case "table": + if (tb.getDocument().quirksMode() != Document.QuirksMode.quirks && tb.inButtonScope("p")) { + tb.processEndTag("p"); + } tb.insert(startTag); tb.framesetOk(false); - } - } else if (StringUtil.inSorted(name, Constants.Formatters)) { - tb.reconstructFormattingElements(); - Element el = tb.insert(startTag); - tb.pushActiveFormattingElements(el); - } else if (name.equals("nobr")) { - tb.reconstructFormattingElements(); - if (tb.inScope("nobr")) { - tb.error(this); - tb.processEndTag("nobr"); + tb.transition(InTable); + break; + case "input": tb.reconstructFormattingElements(); - } - Element el = tb.insert(startTag); - tb.pushActiveFormattingElements(el); - } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { - tb.reconstructFormattingElements(); - tb.insert(startTag); - tb.insertMarkerToFormattingElements(); - tb.framesetOk(false); - } else if (name.equals("table")) { - if (tb.getDocument().quirksMode() != Document.QuirksMode.quirks && tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - tb.framesetOk(false); - tb.transition(InTable); - } else if (name.equals("input")) { - tb.reconstructFormattingElements(); - Element el = tb.insertEmpty(startTag); - if (!el.attr("type").equalsIgnoreCase("hidden")) + el = tb.insertEmpty(startTag); + if (!el.attr("type").equalsIgnoreCase("hidden")) + tb.framesetOk(false); + break; + case "hr": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insertEmpty(startTag); tb.framesetOk(false); - } else if (StringUtil.inSorted(name, Constants.InBodyStartMedia)) { - tb.insertEmpty(startTag); - } else if (name.equals("hr")) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insertEmpty(startTag); - tb.framesetOk(false); - } else if (name.equals("image")) { - if (tb.getFromStack("svg") == null) - return tb.process(startTag.name("img")); // change <image> to <img>, unless in svg - else - tb.insert(startTag); - } else if (name.equals("isindex")) { - // how much do we care about the early 90s? - tb.error(this); - if (tb.getFormElement() != null) - return false; + break; + case "image": + if (tb.getFromStack("svg") == null) + return tb.process(startTag.name("img")); // change <image> to <img>, unless in svg + else + tb.insert(startTag); + break; + case "isindex": + // how much do we care about the early 90s? + tb.error(this); + if (tb.getFormElement() != null) + return false; - tb.processStartTag("form"); - if (startTag.attributes.hasKey("action")) { - Element form = tb.getFormElement(); - form.attr("action", startTag.attributes.get("action")); - } - tb.processStartTag("hr"); - tb.processStartTag("label"); - // hope you like english. - String prompt = startTag.attributes.hasKey("prompt") ? + tb.processStartTag("form"); + if (startTag.attributes.hasKey("action")) { + Element form = tb.getFormElement(); + form.attr("action", startTag.attributes.get("action")); + } + tb.processStartTag("hr"); + tb.processStartTag("label"); + // hope you like english. + String prompt = startTag.attributes.hasKey("prompt") ? startTag.attributes.get("prompt") : "This is a searchable index. Enter search keywords: "; - tb.process(new Token.Character().data(prompt)); + tb.process(new Token.Character().data(prompt)); - // input - Attributes inputAttribs = new Attributes(); - for (Attribute attr : startTag.attributes) { - if (!StringUtil.inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) - inputAttribs.put(attr); - } - inputAttribs.put("name", "isindex"); - tb.processStartTag("input", inputAttribs); - tb.processEndTag("label"); - tb.processStartTag("hr"); - tb.processEndTag("form"); - } else if (name.equals("textarea")) { - tb.insert(startTag); - if (!startTag.isSelfClosing()) { - tb.tokeniser.transition(TokeniserState.Rcdata); - tb.markInsertionMode(); + // input + Attributes inputAttribs = new Attributes(); + for (Attribute attr : startTag.attributes) { + if (!StringUtil.inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) + inputAttribs.put(attr); + } + inputAttribs.put("name", "isindex"); + tb.processStartTag("input", inputAttribs); + tb.processEndTag("label"); + tb.processStartTag("hr"); + tb.processEndTag("form"); + break; + case "textarea": + tb.insert(startTag); + if (!startTag.isSelfClosing()) { + tb.tokeniser.transition(TokeniserState.Rcdata); + tb.markInsertionMode(); + tb.framesetOk(false); + tb.transition(Text); + } + break; + case "xmp": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.reconstructFormattingElements(); + tb.framesetOk(false); + handleRawtext(startTag, tb); + break; + case "iframe": + tb.framesetOk(false); + handleRawtext(startTag, tb); + break; + case "noembed": + // also handle noscript if script enabled + handleRawtext(startTag, tb); + break; + case "select": + tb.reconstructFormattingElements(); + tb.insert(startTag); tb.framesetOk(false); - tb.transition(Text); - } - } else if (name.equals("xmp")) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.reconstructFormattingElements(); - tb.framesetOk(false); - handleRawtext(startTag, tb); - } else if (name.equals("iframe")) { - tb.framesetOk(false); - handleRawtext(startTag, tb); - } else if (name.equals("noembed")) { - // also handle noscript if script enabled - handleRawtext(startTag, tb); - } else if (name.equals("select")) { - tb.reconstructFormattingElements(); - tb.insert(startTag); - tb.framesetOk(false); - HtmlTreeBuilderState state = tb.state(); - if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell)) - tb.transition(InSelectInTable); - else - tb.transition(InSelect); - } else if (StringUtil.inSorted(name, Constants.InBodyStartOptions)) { - if (tb.currentElement().normalName().equals("option")) - tb.processEndTag("option"); - tb.reconstructFormattingElements(); - tb.insert(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartRuby)) { - if (tb.inScope("ruby")) { - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals("ruby")) { + HtmlTreeBuilderState state = tb.state(); + if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell)) + tb.transition(InSelectInTable); + else + tb.transition(InSelect); + break; + case "math": + tb.reconstructFormattingElements(); + // todo: handle A start tag whose tag name is "math" (i.e. foreign, mathml) + tb.insert(startTag); + break; + case "svg": + tb.reconstructFormattingElements(); + // todo: handle A start tag whose tag name is "svg" (xlink, svg) + tb.insert(startTag); + break; + // static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { tb.error(this); - tb.popStackToBefore("ruby"); // i.e. close up to but not include name + tb.pop(); } tb.insert(startTag); - } - } else if (name.equals("math")) { - tb.reconstructFormattingElements(); - // todo: handle A start tag whose tag name is "math" (i.e. foreign, mathml) - tb.insert(startTag); - } else if (name.equals("svg")) { - tb.reconstructFormattingElements(); - // todo: handle A start tag whose tag name is "svg" (xlink, svg) - tb.insert(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { - tb.error(this); - return false; - } else { - tb.reconstructFormattingElements(); - tb.insert(startTag); + break; + // static final String[] InBodyStartPreListing = new String[]{"listing", "pre"}; + case "pre": + case "listing": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + tb.reader.matchConsume("\n"); // ignore LF if next token + tb.framesetOk(false); + break; + // static final String[] DdDt = new String[]{"dd", "dt"}; + case "dd": + case "dt": + tb.framesetOk(false); + stack = tb.getStack(); + for (int i = stack.size() - 1; i > 0; i--) { + el = stack.get(i); + if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { + tb.processEndTag(el.normalName()); + break; + } + if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) + break; + } + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + break; + // static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; + case "optgroup": + case "option": + if (tb.currentElement().normalName().equals("option")) + tb.processEndTag("option"); + tb.reconstructFormattingElements(); + tb.insert(startTag); + break; + // static final String[] InBodyStartRuby = new String[]{"rp", "rt"}; + case "rp": + case "rt": + if (tb.inScope("ruby")) { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals("ruby")) { + tb.error(this); + tb.popStackToBefore("ruby"); // i.e. close up to but not include name + } + tb.insert(startTag); + } + // todo - is this right? drops rp, rt if ruby not in scope? + break; + default: + // todo - bring scan groups in if desired + if (StringUtil.inSorted(name, Constants.InBodyStartEmptyFormatters)) { + tb.reconstructFormattingElements(); + tb.insertEmpty(startTag); + tb.framesetOk(false); + } else if (StringUtil.inSorted(name, Constants.InBodyStartPClosers)) { + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + } else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { + return tb.process(t, InHead); + } else if (StringUtil.inSorted(name, Constants.Formatters)) { + tb.reconstructFormattingElements(); + el = tb.insert(startTag); + tb.pushActiveFormattingElements(el); + } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { + tb.reconstructFormattingElements(); + tb.insert(startTag); + tb.insertMarkerToFormattingElements(); + tb.framesetOk(false); + } else if (StringUtil.inSorted(name, Constants.InBodyStartMedia)) { + tb.insertEmpty(startTag); + } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { + tb.error(this); + return false; + } else { + tb.reconstructFormattingElements(); + tb.insert(startTag); + } } break; case EndTag: Token.EndTag endTag = t.asEndTag(); name = endTag.normalName(); - if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { - // Adoption Agency Algorithm. - for (int i = 0; i < 8; i++) { - Element formatEl = tb.getActiveFormattingElement(name); - if (formatEl == null) - return anyOtherEndTag(t, tb); - else if (!tb.onStack(formatEl)) { + switch (name) { + case "sarcasm": // *sigh* + case "span": + // same as final fall through, but saves short circuit + return anyOtherEndTag(t, tb); + case "li": + if (!tb.inListItemScope(name)) { tb.error(this); - tb.removeFromActiveFormattingElements(formatEl); - return true; - } else if (!tb.inScope(formatEl.normalName())) { + return false; + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + } + break; + case "body": + if (!tb.inScope("body")) { tb.error(this); return false; - } else if (tb.currentElement() != formatEl) + } else { + // todo: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr, body, html + tb.transition(AfterBody); + } + break; + case "html": + boolean notIgnored = tb.processEndTag("body"); + if (notIgnored) + return tb.process(endTag); + break; + case "form": + Element currentForm = tb.getFormElement(); + tb.setFormElement(null); + if (currentForm == null || !tb.inScope(name)) { tb.error(this); - - Element furthestBlock = null; - Element commonAncestor = null; - boolean seenFormattingElement = false; - ArrayList<Element> stack = tb.getStack(); - // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents - // run-aways - final int stackSize = stack.size(); - for (int si = 0; si < stackSize && si < 64; si++) { - Element el = stack.get(si); - if (el == formatEl) { - commonAncestor = stack.get(si - 1); - seenFormattingElement = true; - } else if (seenFormattingElement && tb.isSpecial(el)) { - furthestBlock = el; - break; - } + return false; + } else { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + // remove currentForm from stack. will shift anything under up. + tb.removeFromStack(currentForm); } - if (furthestBlock == null) { - tb.popStackToClose(formatEl.normalName()); - tb.removeFromActiveFormattingElements(formatEl); - return true; + break; + case "p": + if (!tb.inButtonScope(name)) { + tb.error(this); + tb.processStartTag(name); // if no p to close, creates an empty <p></p> + return tb.process(endTag); + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); } + break; + case "dd": + case "dt": + if (!tb.inScope(name)) { + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + } + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + if (!tb.inScope(Constants.Headings)) { + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(Constants.Headings); + } + break; + case "br": + tb.error(this); + tb.processStartTag("br"); + return false; + default: + // todo - move rest to switch if desired + if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { + // Adoption Agency Algorithm. + for (int i = 0; i < 8; i++) { + Element formatEl = tb.getActiveFormattingElement(name); + if (formatEl == null) + return anyOtherEndTag(t, tb); + else if (!tb.onStack(formatEl)) { + tb.error(this); + tb.removeFromActiveFormattingElements(formatEl); + return true; + } else if (!tb.inScope(formatEl.normalName())) { + tb.error(this); + return false; + } else if (tb.currentElement() != formatEl) + tb.error(this); - // todo: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. - // does that mean: int pos of format el in list? - Element node = furthestBlock; - Element lastNode = furthestBlock; - for (int j = 0; j < 3; j++) { - if (tb.onStack(node)) - node = tb.aboveOnStack(node); - if (!tb.isInActiveFormattingElements(node)) { // note no bookmark check - tb.removeFromStack(node); - continue; - } else if (node == formatEl) - break; + Element furthestBlock = null; + Element commonAncestor = null; + boolean seenFormattingElement = false; + stack = tb.getStack(); + // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents + // run-aways + final int stackSize = stack.size(); + for (int si = 0; si < stackSize && si < 64; si++) { + el = stack.get(si); + if (el == formatEl) { + commonAncestor = stack.get(si - 1); + seenFormattingElement = true; + } else if (seenFormattingElement && tb.isSpecial(el)) { + furthestBlock = el; + break; + } + } + if (furthestBlock == null) { + tb.popStackToClose(formatEl.normalName()); + tb.removeFromActiveFormattingElements(formatEl); + return true; + } - Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); - // case will follow the original node (so honours ParseSettings) - tb.replaceActiveFormattingElement(node, replacement); - tb.replaceOnStack(node, replacement); - node = replacement; + // todo: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. + // does that mean: int pos of format el in list? + Element node = furthestBlock; + Element lastNode = furthestBlock; + for (int j = 0; j < 3; j++) { + if (tb.onStack(node)) + node = tb.aboveOnStack(node); + if (!tb.isInActiveFormattingElements(node)) { // note no bookmark check + tb.removeFromStack(node); + continue; + } else if (node == formatEl) + break; - if (lastNode == furthestBlock) { - // todo: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. - // not getting how this bookmark both straddles the element above, but is inbetween here... - } - if (lastNode.parent() != null) - lastNode.remove(); - node.appendChild(lastNode); + Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); + // case will follow the original node (so honours ParseSettings) + tb.replaceActiveFormattingElement(node, replacement); + tb.replaceOnStack(node, replacement); + node = replacement; - lastNode = node; - } + //noinspection StatementWithEmptyBody + if (lastNode == furthestBlock) { + // todo: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. + // not getting how this bookmark both straddles the element above, but is inbetween here... + } + if (lastNode.parent() != null) + lastNode.remove(); + node.appendChild(lastNode); - if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { - if (lastNode.parent() != null) - lastNode.remove(); - tb.insertInFosterParent(lastNode); - } else { - if (lastNode.parent() != null) - lastNode.remove(); - commonAncestor.appendChild(lastNode); - } + lastNode = node; + } - Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); - adopter.attributes().addAll(formatEl.attributes()); - Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); - for (Node childNode : childNodes) { - adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. - } - furthestBlock.appendChild(adopter); - tb.removeFromActiveFormattingElements(formatEl); - // todo: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. - tb.removeFromStack(formatEl); - tb.insertOnStackAfter(furthestBlock, adopter); - } - } else if (StringUtil.inSorted(name, Constants.InBodyEndClosers)) { - if (!tb.inScope(name)) { - // nothing to close - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - } else if (name.equals("span")) { - // same as final fall through, but saves short circuit - return anyOtherEndTag(t, tb); - } else if (name.equals("li")) { - if (!tb.inListItemScope(name)) { - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - } else if (name.equals("body")) { - if (!tb.inScope("body")) { - tb.error(this); - return false; - } else { - // todo: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr, body, html - tb.transition(AfterBody); - } - } else if (name.equals("html")) { - boolean notIgnored = tb.processEndTag("body"); - if (notIgnored) - return tb.process(endTag); - } else if (name.equals("form")) { - Element currentForm = tb.getFormElement(); - tb.setFormElement(null); - if (currentForm == null || !tb.inScope(name)) { - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - // remove currentForm from stack. will shift anything under up. - tb.removeFromStack(currentForm); - } - } else if (name.equals("p")) { - if (!tb.inButtonScope(name)) { - tb.error(this); - tb.processStartTag(name); // if no p to close, creates an empty <p></p> - return tb.process(endTag); - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - } else if (StringUtil.inSorted(name, Constants.DdDt)) { - if (!tb.inScope(name)) { - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - } else if (StringUtil.inSorted(name, Constants.Headings)) { - if (!tb.inScope(Constants.Headings)) { - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(Constants.Headings); - } - } else if (name.equals("sarcasm")) { - // *sigh* - return anyOtherEndTag(t, tb); - } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { - if (!tb.inScope("name")) { - if (!tb.inScope(name)) { - tb.error(this); - return false; + if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { + if (lastNode.parent() != null) + lastNode.remove(); + tb.insertInFosterParent(lastNode); + } else { + if (lastNode.parent() != null) + lastNode.remove(); + commonAncestor.appendChild(lastNode); + } + + Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); + adopter.attributes().addAll(formatEl.attributes()); + Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); + for (Node childNode : childNodes) { + adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. + } + furthestBlock.appendChild(adopter); + tb.removeFromActiveFormattingElements(formatEl); + // todo: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. + tb.removeFromStack(formatEl); + tb.insertOnStackAfter(furthestBlock, adopter); + } + } else if (StringUtil.inSorted(name, Constants.InBodyEndClosers)) { + if (!tb.inScope(name)) { + // nothing to close + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + } + } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { + if (!tb.inScope("name")) { + if (!tb.inScope(name)) { + tb.error(this); + return false; + } + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + tb.clearFormattingElementsToLastMarker(); + } + } else { + return anyOtherEndTag(t, tb); } - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - tb.clearFormattingElementsToLastMarker(); - } - } else if (name.equals("br")) { - tb.error(this); - tb.processStartTag("br"); - return false; - } else { - return anyOtherEndTag(t, tb); } - break; case EOF: // todo: error if stack contains something not dd, dt, li, p, tbody, td, tfoot, th, thead, tr, body, html @@ -907,37 +971,34 @@ boolean anythingElse(Token t, HtmlTreeBuilder tb) { }, InTableText { boolean process(Token t, HtmlTreeBuilder tb) { - switch (t.type) { - case Character: - Token.Character c = t.asCharacter(); - if (c.getData().equals(nullString)) { - tb.error(this); - return false; - } else { - tb.getPendingTableCharacters().add(c.getData()); - } - break; - default: - // todo - don't really like the way these table character data lists are built - if (tb.getPendingTableCharacters().size() > 0) { - for (String character : tb.getPendingTableCharacters()) { - if (!isWhitespace(character)) { - // InTable anything else section: - tb.error(this); - if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { - tb.setFosterInserts(true); - tb.process(new Token.Character().data(character), InBody); - tb.setFosterInserts(false); - } else { - tb.process(new Token.Character().data(character), InBody); - } - } else - tb.insert(new Token.Character().data(character)); - } - tb.newPendingTableCharacters(); + if (t.type == Token.TokenType.Character) { + Token.Character c = t.asCharacter(); + if (c.getData().equals(nullString)) { + tb.error(this); + return false; + } else { + tb.getPendingTableCharacters().add(c.getData()); + } + } else {// todo - don't really like the way these table character data lists are built + if (tb.getPendingTableCharacters().size() > 0) { + for (String character : tb.getPendingTableCharacters()) { + if (!isWhitespace(character)) { + // InTable anything else section: + tb.error(this); + if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { + tb.setFosterInserts(true); + tb.process(new Token.Character().data(character), InBody); + tb.setFosterInserts(false); + } else { + tb.process(new Token.Character().data(character), InBody); + } + } else + tb.insert(new Token.Character().data(character)); } - tb.transition(tb.originalState()); - return tb.process(t); + tb.newPendingTableCharacters(); + } + tb.transition(tb.originalState()); + return tb.process(t); } return true; } @@ -1466,7 +1527,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } }; - private static String nullString = String.valueOf('\u0000'); + private static final String nullString = String.valueOf('\u0000'); abstract boolean process(Token t, HtmlTreeBuilder tb); From 1f5ffb85c35e0fd42fcefc1eb562e36d809a3d15 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 23 Feb 2020 09:12:19 -0800 Subject: [PATCH 417/774] Pulled out InBody start and end sequences Bit easier to read / measure. Didn't see perf change (good) --- .../jsoup/parser/HtmlTreeBuilderState.java | 1080 +++++++++-------- 1 file changed, 548 insertions(+), 532 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 20dc31f65e..68aa1b93ba 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -250,9 +250,6 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { }, InBody { boolean process(Token t, HtmlTreeBuilder tb) { - ArrayList<Element> stack; - Element el; - switch (t.type) { case Character: { Token.Character c = t.asCharacter(); @@ -279,560 +276,480 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } case StartTag: - Token.StartTag startTag = t.asStartTag(); - String name = startTag.normalName(); + return inBodyStartTag(t, tb); + case EndTag: + return inBodyEndTag(t, tb); + case EOF: + // todo: error if stack contains something not dd, dt, li, p, tbody, td, tfoot, th, thead, tr, body, html + // stop parsing + break; + } + return true; + } - switch (name) { - case "a": - if (tb.getActiveFormattingElement("a") != null) { - tb.error(this); - tb.processEndTag("a"); + private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { + final Token.StartTag startTag = t.asStartTag(); + final String name = startTag.normalName(); + final ArrayList<Element> stack; + Element el; - // still on stack? - Element remainingA = tb.getFromStack("a"); - if (remainingA != null) { - tb.removeFromActiveFormattingElements(remainingA); - tb.removeFromStack(remainingA); - } - } - tb.reconstructFormattingElements(); - Element a = tb.insert(startTag); - tb.pushActiveFormattingElements(a); - break; - case "span": - // same as final else, but short circuits lots of checks - tb.reconstructFormattingElements(); - tb.insert(startTag); - break; - case "li": - tb.framesetOk(false); - stack = tb.getStack(); - for (int i = stack.size() - 1; i > 0; i--) { - el = stack.get(i); - if (el.normalName().equals("li")) { - tb.processEndTag("li"); - break; - } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) - break; - } - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - break; - case "html": - tb.error(this); - // merge attributes onto real html - Element html = tb.getStack().get(0); - for (Attribute attribute : startTag.getAttributes()) { - if (!html.hasAttr(attribute.getKey())) - html.attributes().put(attribute); - } - break; - case "body": - tb.error(this); - stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { - // only in fragment case - return false; // ignore - } else { - tb.framesetOk(false); - Element body = stack.get(1); - for (Attribute attribute : startTag.getAttributes()) { - if (!body.hasAttr(attribute.getKey())) - body.attributes().put(attribute); - } - } - break; - case "frameset": - tb.error(this); - stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { - // only in fragment case - return false; // ignore - } else if (!tb.framesetOk()) { - return false; // ignore frameset - } else { - Element second = stack.get(1); - if (second.parent() != null) - second.remove(); - // pop up to html element - while (stack.size() > 1) - stack.remove(stack.size() - 1); - tb.insert(startTag); - tb.transition(InFrameset); - } - break; - case "form": - if (tb.getFormElement() != null) { - tb.error(this); - return false; - } - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insertForm(startTag, true); - break; - case "plaintext": - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - tb.tokeniser.transition(TokeniserState.PLAINTEXT); // once in, never gets out - break; - case "button": - if (tb.inButtonScope("button")) { - // close and reprocess - tb.error(this); - tb.processEndTag("button"); - tb.process(startTag); - } else { - tb.reconstructFormattingElements(); - tb.insert(startTag); - tb.framesetOk(false); - } - break; - case "nobr": - tb.reconstructFormattingElements(); - if (tb.inScope("nobr")) { - tb.error(this); - tb.processEndTag("nobr"); - tb.reconstructFormattingElements(); - } - el = tb.insert(startTag); - tb.pushActiveFormattingElements(el); - break; - case "table": - if (tb.getDocument().quirksMode() != Document.QuirksMode.quirks && tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - tb.framesetOk(false); - tb.transition(InTable); - break; - case "input": - tb.reconstructFormattingElements(); - el = tb.insertEmpty(startTag); - if (!el.attr("type").equalsIgnoreCase("hidden")) - tb.framesetOk(false); - break; - case "hr": - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insertEmpty(startTag); - tb.framesetOk(false); + switch (name) { + case "a": + if (tb.getActiveFormattingElement("a") != null) { + tb.error(this); + tb.processEndTag("a"); + + // still on stack? + Element remainingA = tb.getFromStack("a"); + if (remainingA != null) { + tb.removeFromActiveFormattingElements(remainingA); + tb.removeFromStack(remainingA); + } + } + tb.reconstructFormattingElements(); + el = tb.insert(startTag); + tb.pushActiveFormattingElements(el); + break; + case "span": + // same as final else, but short circuits lots of checks + tb.reconstructFormattingElements(); + tb.insert(startTag); + break; + case "li": + tb.framesetOk(false); + stack = tb.getStack(); + for (int i = stack.size() - 1; i > 0; i--) { + el = stack.get(i); + if (el.normalName().equals("li")) { + tb.processEndTag("li"); break; - case "image": - if (tb.getFromStack("svg") == null) - return tb.process(startTag.name("img")); // change <image> to <img>, unless in svg - else - tb.insert(startTag); + } + if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; - case "isindex": - // how much do we care about the early 90s? - tb.error(this); - if (tb.getFormElement() != null) - return false; + } + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + break; + case "html": + tb.error(this); + // merge attributes onto real html + Element html = tb.getStack().get(0); + for (Attribute attribute : startTag.getAttributes()) { + if (!html.hasAttr(attribute.getKey())) + html.attributes().put(attribute); + } + break; + case "body": + tb.error(this); + stack = tb.getStack(); + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { + // only in fragment case + return false; // ignore + } else { + tb.framesetOk(false); + Element body = stack.get(1); + for (Attribute attribute : startTag.getAttributes()) { + if (!body.hasAttr(attribute.getKey())) + body.attributes().put(attribute); + } + } + break; + case "frameset": + tb.error(this); + stack = tb.getStack(); + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { + // only in fragment case + return false; // ignore + } else if (!tb.framesetOk()) { + return false; // ignore frameset + } else { + Element second = stack.get(1); + if (second.parent() != null) + second.remove(); + // pop up to html element + while (stack.size() > 1) + stack.remove(stack.size() - 1); + tb.insert(startTag); + tb.transition(InFrameset); + } + break; + case "form": + if (tb.getFormElement() != null) { + tb.error(this); + return false; + } + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insertForm(startTag, true); + break; + case "plaintext": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + tb.tokeniser.transition(TokeniserState.PLAINTEXT); // once in, never gets out + break; + case "button": + if (tb.inButtonScope("button")) { + // close and reprocess + tb.error(this); + tb.processEndTag("button"); + tb.process(startTag); + } else { + tb.reconstructFormattingElements(); + tb.insert(startTag); + tb.framesetOk(false); + } + break; + case "nobr": + tb.reconstructFormattingElements(); + if (tb.inScope("nobr")) { + tb.error(this); + tb.processEndTag("nobr"); + tb.reconstructFormattingElements(); + } + el = tb.insert(startTag); + tb.pushActiveFormattingElements(el); + break; + case "table": + if (tb.getDocument().quirksMode() != Document.QuirksMode.quirks && tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + tb.framesetOk(false); + tb.transition(InTable); + break; + case "input": + tb.reconstructFormattingElements(); + el = tb.insertEmpty(startTag); + if (!el.attr("type").equalsIgnoreCase("hidden")) + tb.framesetOk(false); + break; + case "hr": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insertEmpty(startTag); + tb.framesetOk(false); + break; + case "image": + if (tb.getFromStack("svg") == null) + return tb.process(startTag.name("img")); // change <image> to <img>, unless in svg + else + tb.insert(startTag); + break; + case "isindex": + // how much do we care about the early 90s? + tb.error(this); + if (tb.getFormElement() != null) + return false; - tb.processStartTag("form"); - if (startTag.attributes.hasKey("action")) { - Element form = tb.getFormElement(); - form.attr("action", startTag.attributes.get("action")); - } - tb.processStartTag("hr"); - tb.processStartTag("label"); - // hope you like english. - String prompt = startTag.attributes.hasKey("prompt") ? - startTag.attributes.get("prompt") : - "This is a searchable index. Enter search keywords: "; + tb.processStartTag("form"); + if (startTag.attributes.hasKey("action")) { + Element form = tb.getFormElement(); + form.attr("action", startTag.attributes.get("action")); + } + tb.processStartTag("hr"); + tb.processStartTag("label"); + // hope you like english. + String prompt = startTag.attributes.hasKey("prompt") ? + startTag.attributes.get("prompt") : + "This is a searchable index. Enter search keywords: "; - tb.process(new Token.Character().data(prompt)); + tb.process(new Token.Character().data(prompt)); - // input - Attributes inputAttribs = new Attributes(); - for (Attribute attr : startTag.attributes) { - if (!StringUtil.inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) - inputAttribs.put(attr); - } - inputAttribs.put("name", "isindex"); - tb.processStartTag("input", inputAttribs); - tb.processEndTag("label"); - tb.processStartTag("hr"); - tb.processEndTag("form"); - break; - case "textarea": - tb.insert(startTag); - if (!startTag.isSelfClosing()) { - tb.tokeniser.transition(TokeniserState.Rcdata); - tb.markInsertionMode(); - tb.framesetOk(false); - tb.transition(Text); - } - break; - case "xmp": - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.reconstructFormattingElements(); - tb.framesetOk(false); - handleRawtext(startTag, tb); - break; - case "iframe": - tb.framesetOk(false); - handleRawtext(startTag, tb); - break; - case "noembed": - // also handle noscript if script enabled - handleRawtext(startTag, tb); - break; - case "select": - tb.reconstructFormattingElements(); - tb.insert(startTag); - tb.framesetOk(false); + // input + Attributes inputAttribs = new Attributes(); + for (Attribute attr : startTag.attributes) { + if (!StringUtil.inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) + inputAttribs.put(attr); + } + inputAttribs.put("name", "isindex"); + tb.processStartTag("input", inputAttribs); + tb.processEndTag("label"); + tb.processStartTag("hr"); + tb.processEndTag("form"); + break; + case "textarea": + tb.insert(startTag); + if (!startTag.isSelfClosing()) { + tb.tokeniser.transition(TokeniserState.Rcdata); + tb.markInsertionMode(); + tb.framesetOk(false); + tb.transition(Text); + } + break; + case "xmp": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.reconstructFormattingElements(); + tb.framesetOk(false); + handleRawtext(startTag, tb); + break; + case "iframe": + tb.framesetOk(false); + handleRawtext(startTag, tb); + break; + case "noembed": + // also handle noscript if script enabled + handleRawtext(startTag, tb); + break; + case "select": + tb.reconstructFormattingElements(); + tb.insert(startTag); + tb.framesetOk(false); - HtmlTreeBuilderState state = tb.state(); - if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell)) - tb.transition(InSelectInTable); - else - tb.transition(InSelect); - break; - case "math": - tb.reconstructFormattingElements(); - // todo: handle A start tag whose tag name is "math" (i.e. foreign, mathml) - tb.insert(startTag); - break; - case "svg": - tb.reconstructFormattingElements(); - // todo: handle A start tag whose tag name is "svg" (xlink, svg) - tb.insert(startTag); - break; - // static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { - tb.error(this); - tb.pop(); - } - tb.insert(startTag); - break; - // static final String[] InBodyStartPreListing = new String[]{"listing", "pre"}; - case "pre": - case "listing": - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - tb.reader.matchConsume("\n"); // ignore LF if next token - tb.framesetOk(false); - break; - // static final String[] DdDt = new String[]{"dd", "dt"}; - case "dd": - case "dt": - tb.framesetOk(false); - stack = tb.getStack(); - for (int i = stack.size() - 1; i > 0; i--) { - el = stack.get(i); - if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { - tb.processEndTag(el.normalName()); - break; - } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) - break; - } - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - break; - // static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; - case "optgroup": - case "option": - if (tb.currentElement().normalName().equals("option")) - tb.processEndTag("option"); - tb.reconstructFormattingElements(); - tb.insert(startTag); + HtmlTreeBuilderState state = tb.state(); + if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell)) + tb.transition(InSelectInTable); + else + tb.transition(InSelect); + break; + case "math": + tb.reconstructFormattingElements(); + // todo: handle A start tag whose tag name is "math" (i.e. foreign, mathml) + tb.insert(startTag); + break; + case "svg": + tb.reconstructFormattingElements(); + // todo: handle A start tag whose tag name is "svg" (xlink, svg) + tb.insert(startTag); + break; + // static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { + tb.error(this); + tb.pop(); + } + tb.insert(startTag); + break; + // static final String[] InBodyStartPreListing = new String[]{"listing", "pre"}; + case "pre": + case "listing": + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + tb.reader.matchConsume("\n"); // ignore LF if next token + tb.framesetOk(false); + break; + // static final String[] DdDt = new String[]{"dd", "dt"}; + case "dd": + case "dt": + tb.framesetOk(false); + stack = tb.getStack(); + for (int i = stack.size() - 1; i > 0; i--) { + el = stack.get(i); + if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { + tb.processEndTag(el.normalName()); break; - // static final String[] InBodyStartRuby = new String[]{"rp", "rt"}; - case "rp": - case "rt": - if (tb.inScope("ruby")) { - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals("ruby")) { - tb.error(this); - tb.popStackToBefore("ruby"); // i.e. close up to but not include name - } - tb.insert(startTag); - } - // todo - is this right? drops rp, rt if ruby not in scope? + } + if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; - default: - // todo - bring scan groups in if desired - if (StringUtil.inSorted(name, Constants.InBodyStartEmptyFormatters)) { - tb.reconstructFormattingElements(); - tb.insertEmpty(startTag); - tb.framesetOk(false); - } else if (StringUtil.inSorted(name, Constants.InBodyStartPClosers)) { - if (tb.inButtonScope("p")) { - tb.processEndTag("p"); - } - tb.insert(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { - return tb.process(t, InHead); - } else if (StringUtil.inSorted(name, Constants.Formatters)) { - tb.reconstructFormattingElements(); - el = tb.insert(startTag); - tb.pushActiveFormattingElements(el); - } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { - tb.reconstructFormattingElements(); - tb.insert(startTag); - tb.insertMarkerToFormattingElements(); - tb.framesetOk(false); - } else if (StringUtil.inSorted(name, Constants.InBodyStartMedia)) { - tb.insertEmpty(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { - tb.error(this); - return false; - } else { - tb.reconstructFormattingElements(); - tb.insert(startTag); - } } + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + break; + // static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; + case "optgroup": + case "option": + if (tb.currentElement().normalName().equals("option")) + tb.processEndTag("option"); + tb.reconstructFormattingElements(); + tb.insert(startTag); break; + // static final String[] InBodyStartRuby = new String[]{"rp", "rt"}; + case "rp": + case "rt": + if (tb.inScope("ruby")) { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals("ruby")) { + tb.error(this); + tb.popStackToBefore("ruby"); // i.e. close up to but not include name + } + tb.insert(startTag); + } + // todo - is this right? drops rp, rt if ruby not in scope? + break; + default: + // todo - bring scan groups in if desired + if (StringUtil.inSorted(name, Constants.InBodyStartEmptyFormatters)) { + tb.reconstructFormattingElements(); + tb.insertEmpty(startTag); + tb.framesetOk(false); + } else if (StringUtil.inSorted(name, Constants.InBodyStartPClosers)) { + if (tb.inButtonScope("p")) { + tb.processEndTag("p"); + } + tb.insert(startTag); + } else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { + return tb.process(t, InHead); + } else if (StringUtil.inSorted(name, Constants.Formatters)) { + tb.reconstructFormattingElements(); + el = tb.insert(startTag); + tb.pushActiveFormattingElements(el); + } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { + tb.reconstructFormattingElements(); + tb.insert(startTag); + tb.insertMarkerToFormattingElements(); + tb.framesetOk(false); + } else if (StringUtil.inSorted(name, Constants.InBodyStartMedia)) { + tb.insertEmpty(startTag); + } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { + tb.error(this); + return false; + } else { + tb.reconstructFormattingElements(); + tb.insert(startTag); + } + } + return true; + } - case EndTag: - Token.EndTag endTag = t.asEndTag(); - name = endTag.normalName(); - switch (name) { - case "sarcasm": // *sigh* - case "span": - // same as final fall through, but saves short circuit - return anyOtherEndTag(t, tb); - case "li": - if (!tb.inListItemScope(name)) { - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - break; - case "body": - if (!tb.inScope("body")) { - tb.error(this); - return false; - } else { - // todo: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr, body, html - tb.transition(AfterBody); - } - break; - case "html": - boolean notIgnored = tb.processEndTag("body"); - if (notIgnored) - return tb.process(endTag); - break; - case "form": - Element currentForm = tb.getFormElement(); - tb.setFormElement(null); - if (currentForm == null || !tb.inScope(name)) { - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - // remove currentForm from stack. will shift anything under up. - tb.removeFromStack(currentForm); - } - break; - case "p": - if (!tb.inButtonScope(name)) { + private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { + final Token.EndTag endTag = t.asEndTag(); + final String name = endTag.normalName(); + + switch (name) { + case "sarcasm": // *sigh* + case "span": + // same as final fall through, but saves short circuit + return anyOtherEndTag(t, tb); + case "li": + if (!tb.inListItemScope(name)) { + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + } + break; + case "body": + if (!tb.inScope("body")) { + tb.error(this); + return false; + } else { + // todo: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr, body, html + tb.transition(AfterBody); + } + break; + case "html": + boolean notIgnored = tb.processEndTag("body"); + if (notIgnored) + return tb.process(endTag); + break; + case "form": + Element currentForm = tb.getFormElement(); + tb.setFormElement(null); + if (currentForm == null || !tb.inScope(name)) { + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + // remove currentForm from stack. will shift anything under up. + tb.removeFromStack(currentForm); + } + break; + case "p": + if (!tb.inButtonScope(name)) { + tb.error(this); + tb.processStartTag(name); // if no p to close, creates an empty <p></p> + return tb.process(endTag); + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + } + break; + case "dd": + case "dt": + if (!tb.inScope(name)) { + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(name); + } + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + if (!tb.inScope(Constants.Headings)) { + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(name); + if (!tb.currentElement().normalName().equals(name)) + tb.error(this); + tb.popStackToClose(Constants.Headings); + } + break; + case "br": + tb.error(this); + tb.processStartTag("br"); + return false; + default: + // todo - move rest to switch if desired + if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { + return inBodyEndTagAdoption(t, tb); + } else if (StringUtil.inSorted(name, Constants.InBodyEndClosers)) { + if (!tb.inScope(name)) { + // nothing to close + tb.error(this); + return false; + } else { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals(name)) tb.error(this); - tb.processStartTag(name); // if no p to close, creates an empty <p></p> - return tb.process(endTag); - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - break; - case "dd": - case "dt": + tb.popStackToClose(name); + } + } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { + if (!tb.inScope("name")) { if (!tb.inScope(name)) { tb.error(this); return false; - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); } - break; - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - if (!tb.inScope(Constants.Headings)) { + tb.generateImpliedEndTags(); + if (!tb.currentElement().normalName().equals(name)) tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(Constants.Headings); - } - break; - case "br": - tb.error(this); - tb.processStartTag("br"); - return false; - default: - // todo - move rest to switch if desired - if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { - // Adoption Agency Algorithm. - for (int i = 0; i < 8; i++) { - Element formatEl = tb.getActiveFormattingElement(name); - if (formatEl == null) - return anyOtherEndTag(t, tb); - else if (!tb.onStack(formatEl)) { - tb.error(this); - tb.removeFromActiveFormattingElements(formatEl); - return true; - } else if (!tb.inScope(formatEl.normalName())) { - tb.error(this); - return false; - } else if (tb.currentElement() != formatEl) - tb.error(this); - - Element furthestBlock = null; - Element commonAncestor = null; - boolean seenFormattingElement = false; - stack = tb.getStack(); - // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents - // run-aways - final int stackSize = stack.size(); - for (int si = 0; si < stackSize && si < 64; si++) { - el = stack.get(si); - if (el == formatEl) { - commonAncestor = stack.get(si - 1); - seenFormattingElement = true; - } else if (seenFormattingElement && tb.isSpecial(el)) { - furthestBlock = el; - break; - } - } - if (furthestBlock == null) { - tb.popStackToClose(formatEl.normalName()); - tb.removeFromActiveFormattingElements(formatEl); - return true; - } - - // todo: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. - // does that mean: int pos of format el in list? - Element node = furthestBlock; - Element lastNode = furthestBlock; - for (int j = 0; j < 3; j++) { - if (tb.onStack(node)) - node = tb.aboveOnStack(node); - if (!tb.isInActiveFormattingElements(node)) { // note no bookmark check - tb.removeFromStack(node); - continue; - } else if (node == formatEl) - break; - - Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); - // case will follow the original node (so honours ParseSettings) - tb.replaceActiveFormattingElement(node, replacement); - tb.replaceOnStack(node, replacement); - node = replacement; - - //noinspection StatementWithEmptyBody - if (lastNode == furthestBlock) { - // todo: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. - // not getting how this bookmark both straddles the element above, but is inbetween here... - } - if (lastNode.parent() != null) - lastNode.remove(); - node.appendChild(lastNode); - - lastNode = node; - } - - if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { - if (lastNode.parent() != null) - lastNode.remove(); - tb.insertInFosterParent(lastNode); - } else { - if (lastNode.parent() != null) - lastNode.remove(); - commonAncestor.appendChild(lastNode); - } - - Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); - adopter.attributes().addAll(formatEl.attributes()); - Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); - for (Node childNode : childNodes) { - adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. - } - furthestBlock.appendChild(adopter); - tb.removeFromActiveFormattingElements(formatEl); - // todo: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. - tb.removeFromStack(formatEl); - tb.insertOnStackAfter(furthestBlock, adopter); - } - } else if (StringUtil.inSorted(name, Constants.InBodyEndClosers)) { - if (!tb.inScope(name)) { - // nothing to close - tb.error(this); - return false; - } else { - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - } - } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { - if (!tb.inScope("name")) { - if (!tb.inScope(name)) { - tb.error(this); - return false; - } - tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) - tb.error(this); - tb.popStackToClose(name); - tb.clearFormattingElementsToLastMarker(); - } - } else { - return anyOtherEndTag(t, tb); - } + tb.popStackToClose(name); + tb.clearFormattingElementsToLastMarker(); + } + } else { + return anyOtherEndTag(t, tb); } - break; - case EOF: - // todo: error if stack contains something not dd, dt, li, p, tbody, td, tfoot, th, thead, tr, body, html - // stop parsing - break; } return true; } boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { - String name = t.asEndTag().normalName; // case insensitive search - goal is to preserve output case, not for the parse to be case sensitive - ArrayList<Element> stack = tb.getStack(); - for (int pos = stack.size() -1; pos >= 0; pos--) { + final String name = t.asEndTag().normalName; // case insensitive search - goal is to preserve output case, not for the parse to be case sensitive + final ArrayList<Element> stack = tb.getStack(); + for (int pos = stack.size() - 1; pos >= 0; pos--) { Element node = stack.get(pos); if (node.normalName().equals(name)) { tb.generateImpliedEndTags(name); @@ -849,6 +766,105 @@ boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { } return true; } + + // Adoption Agency Algorithm. + private boolean inBodyEndTagAdoption(Token t, HtmlTreeBuilder tb) { + final Token.EndTag endTag = t.asEndTag(); + final String name = endTag.normalName(); + + final ArrayList<Element> stack = tb.getStack(); + Element el; + for (int i = 0; i < 8; i++) { + Element formatEl = tb.getActiveFormattingElement(name); + if (formatEl == null) + return anyOtherEndTag(t, tb); + else if (!tb.onStack(formatEl)) { + tb.error(this); + tb.removeFromActiveFormattingElements(formatEl); + return true; + } else if (!tb.inScope(formatEl.normalName())) { + tb.error(this); + return false; + } else if (tb.currentElement() != formatEl) + tb.error(this); + + Element furthestBlock = null; + Element commonAncestor = null; + boolean seenFormattingElement = false; + // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents + // run-aways + final int stackSize = stack.size(); + for (int si = 0; si < stackSize && si < 64; si++) { + el = stack.get(si); + if (el == formatEl) { + commonAncestor = stack.get(si - 1); + seenFormattingElement = true; + } else if (seenFormattingElement && tb.isSpecial(el)) { + furthestBlock = el; + break; + } + } + if (furthestBlock == null) { + tb.popStackToClose(formatEl.normalName()); + tb.removeFromActiveFormattingElements(formatEl); + return true; + } + + // todo: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. + // does that mean: int pos of format el in list? + Element node = furthestBlock; + Element lastNode = furthestBlock; + for (int j = 0; j < 3; j++) { + if (tb.onStack(node)) + node = tb.aboveOnStack(node); + if (!tb.isInActiveFormattingElements(node)) { // note no bookmark check + tb.removeFromStack(node); + continue; + } else if (node == formatEl) + break; + + Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); + // case will follow the original node (so honours ParseSettings) + tb.replaceActiveFormattingElement(node, replacement); + tb.replaceOnStack(node, replacement); + node = replacement; + + //noinspection StatementWithEmptyBody + if (lastNode == furthestBlock) { + // todo: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. + // not getting how this bookmark both straddles the element above, but is inbetween here... + } + if (lastNode.parent() != null) + lastNode.remove(); + node.appendChild(lastNode); + + lastNode = node; + } + + if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { + if (lastNode.parent() != null) + lastNode.remove(); + tb.insertInFosterParent(lastNode); + } else { + if (lastNode.parent() != null) + lastNode.remove(); + commonAncestor.appendChild(lastNode); + } + + Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); + adopter.attributes().addAll(formatEl.attributes()); + Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); + for (Node childNode : childNodes) { + adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. + } + furthestBlock.appendChild(adopter); + tb.removeFromActiveFormattingElements(formatEl); + // todo: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. + tb.removeFromStack(formatEl); + tb.insertOnStackAfter(furthestBlock, adopter); + } + return true; + } }, Text { // in script, style etc. normally treated as data tags From 696461b3288ad792ae41d057693a07b7e01d0635 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 23 Feb 2020 10:57:23 -0800 Subject: [PATCH 418/774] Perf - catch comments in one hit Vs always String Buffer appending. Also remove a formatter which on some files would create a lot of garbage. --- src/main/java/org/jsoup/parser/Token.java | 31 +++++++++++++++++-- src/main/java/org/jsoup/parser/Tokeniser.java | 2 +- .../java/org/jsoup/parser/TokeniserState.java | 30 +++++++++--------- .../java/org/jsoup/parser/HtmlParserTest.java | 4 +-- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 4e28c30655..f9061377fe 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -257,12 +257,14 @@ public String toString() { } final static class Comment extends Token { - final StringBuilder data = new StringBuilder(); + private final StringBuilder data = new StringBuilder(); + private String dataS; // try to get in one shot boolean bogus = false; @Override Token reset() { reset(data); + dataS = null; bogus = false; return this; } @@ -272,9 +274,34 @@ Token reset() { } String getData() { - return data.toString(); + return dataS != null ? dataS : data.toString(); } + final Comment append(String append) { + ensureData(); + if (data.length() == 0) { + dataS = append; + } else { + data.append(append); + } + return this; + } + + final Comment append(char append) { + ensureData(); + data.append(append); + return this; + } + + private void ensureData() { + // if on second hit, we'll need to move to the builder + if (dataS != null) { + data.append(dataS); + dataS = null; + } + } + + @Override public String toString() { return "<!--" + getData() + "-->"; diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 19fb52a445..385be0a752 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -190,7 +190,7 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in if (!found) { reader.rewindToMark(); if (looksLegit) // named with semicolon - characterReferenceError(String.format("invalid named reference '%s'", nameRef)); + characterReferenceError("invalid named reference"); return null; } if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny('=', '-', '_'))) { diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 266bf03202..d81cecce06 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -912,7 +912,7 @@ void read(Tokeniser t, CharacterReader r) { // todo: handle bogus comment starting from eof. when does that trigger? // rewind to capture character that lead us here r.unconsume(); - t.commentPending.data.append(r.consumeTo('>')); + t.commentPending.append(r.consumeTo('>')); // todo: replace nullChar with replaceChar char next = r.consume(); if (next == '>' || next == eof) { @@ -950,7 +950,7 @@ void read(Tokeniser t, CharacterReader r) { break; case nullChar: t.error(this); - t.commentPending.data.append(replacementChar); + t.commentPending.append(replacementChar); t.transition(Comment); break; case '>': @@ -964,7 +964,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.commentPending.data.append(c); + r.unconsume(); t.transition(Comment); } } @@ -978,7 +978,7 @@ void read(Tokeniser t, CharacterReader r) { break; case nullChar: t.error(this); - t.commentPending.data.append(replacementChar); + t.commentPending.append(replacementChar); t.transition(Comment); break; case '>': @@ -992,7 +992,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.commentPending.data.append(c); + t.commentPending.append(c); t.transition(Comment); } } @@ -1007,7 +1007,7 @@ void read(Tokeniser t, CharacterReader r) { case nullChar: t.error(this); r.advance(); - t.commentPending.data.append(replacementChar); + t.commentPending.append(replacementChar); break; case eof: t.eofError(this); @@ -1015,7 +1015,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.commentPending.data.append(r.consumeToAny('-', nullChar)); + t.commentPending.append(r.consumeToAny('-', nullChar)); } } }, @@ -1028,7 +1028,7 @@ void read(Tokeniser t, CharacterReader r) { break; case nullChar: t.error(this); - t.commentPending.data.append('-').append(replacementChar); + t.commentPending.append('-').append(replacementChar); t.transition(Comment); break; case eof: @@ -1037,7 +1037,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.commentPending.data.append('-').append(c); + t.commentPending.append('-').append(c); t.transition(Comment); } } @@ -1052,7 +1052,7 @@ void read(Tokeniser t, CharacterReader r) { break; case nullChar: t.error(this); - t.commentPending.data.append("--").append(replacementChar); + t.commentPending.append("--").append(replacementChar); t.transition(Comment); break; case '!': @@ -1061,7 +1061,7 @@ void read(Tokeniser t, CharacterReader r) { break; case '-': t.error(this); - t.commentPending.data.append('-'); + t.commentPending.append('-'); break; case eof: t.eofError(this); @@ -1070,7 +1070,7 @@ void read(Tokeniser t, CharacterReader r) { break; default: t.error(this); - t.commentPending.data.append("--").append(c); + t.commentPending.append("--").append(c); t.transition(Comment); } } @@ -1080,7 +1080,7 @@ void read(Tokeniser t, CharacterReader r) { char c = r.consume(); switch (c) { case '-': - t.commentPending.data.append("--!"); + t.commentPending.append("--!"); t.transition(CommentEndDash); break; case '>': @@ -1089,7 +1089,7 @@ void read(Tokeniser t, CharacterReader r) { break; case nullChar: t.error(this); - t.commentPending.data.append("--!").append(replacementChar); + t.commentPending.append("--!").append(replacementChar); t.transition(Comment); break; case eof: @@ -1098,7 +1098,7 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.commentPending.data.append("--!").append(c); + t.commentPending.append("--!").append(c); t.transition(Comment); } } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index c93bcd8fdc..02c108d61d 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -827,7 +827,7 @@ public class HtmlParserTest { assertEquals(5, errors.size()); assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("36: Invalid character reference: invalid named reference 'arrgh'", errors.get(2).toString()); + assertEquals("36: Invalid character reference: invalid named reference", errors.get(2).toString()); assertEquals("50: Tag cannot be self closing; not a void tag", errors.get(3).toString()); assertEquals("61: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(4).toString()); } @@ -841,7 +841,7 @@ public class HtmlParserTest { assertEquals(3, errors.size()); assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("36: Invalid character reference: invalid named reference 'arrgh'", errors.get(2).toString()); + assertEquals("36: Invalid character reference: invalid named reference", errors.get(2).toString()); } @Test public void noErrorsByDefault() { From ebd2a773d67e8d1d042607fd2458f92e18b9203b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 23 Feb 2020 11:46:01 -0800 Subject: [PATCH 419/774] Don't copy String if a char buf is available --- src/main/java/org/jsoup/helper/DataUtil.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index bd4ec6c701..568df983f5 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -12,6 +12,7 @@ import org.jsoup.select.Elements; import java.io.BufferedReader; +import java.io.CharArrayReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -20,6 +21,7 @@ import java.io.OutputStream; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.util.Locale; @@ -112,9 +114,12 @@ static Document parseInputStream(InputStream input, String charsetName, String b charsetName = bomCharset.charset; if (charsetName == null) { // determine from meta. safe first parse as UTF-8 - String docData = Charset.forName(defaultCharset).decode(firstBytes).toString(); try { - doc = parser.parseInput(docData, baseUri); + CharBuffer defaultDecoded = Charset.forName(defaultCharset).decode(firstBytes); + if (defaultDecoded.hasArray()) + doc = parser.parseInput(new CharArrayReader(defaultDecoded.array()), baseUri); + else + doc = parser.parseInput(defaultDecoded.toString(), baseUri); } catch (UncheckedIOException e) { throw e.ioException(); } From 51278145ef4f55e7b45276ac071332a53e6b19a6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 23 Feb 2020 13:44:20 -0800 Subject: [PATCH 420/774] Save a little time - that verification hasn't fired to date --- src/main/java/org/jsoup/parser/Tokeniser.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 385be0a752..788d8bd817 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -53,13 +53,8 @@ final class Tokeniser { } Token read() { - final CharacterReader r = this.reader; - final int pos = r.pos(); // count how many reads we do in a row without making progress, and bail if stuck in a loop - int consecutiveReads = 0; while (!isEmitPending) { - state.read(this, r); - if (++consecutiveReads > 10 && r.pos() <= pos) - Validate.wtf("BUG: Not making progress from state: " + this.state.name() + " with current char=" + r.current()); + state.read(this, reader); } // if emit is pending, a non-character token was found: return any chars in buffer, and leave token for next read: @@ -80,7 +75,7 @@ Token read() { } void emit(Token token) { - Validate.isFalse(isEmitPending, "There is an unread token pending!"); + Validate.isFalse(isEmitPending); emitPending = token; isEmitPending = true; From 328f2e4ea8edb4c993051ba6834f12cde513657e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 29 Feb 2020 14:57:21 -0800 Subject: [PATCH 421/774] Use canned arrays for all searches Saves GC --- .../jsoup/parser/HtmlTreeBuilderState.java | 133 ++++++++++-------- .../parser/HtmlTreeBuilderStateTest.java | 61 ++++---- 2 files changed, 107 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 68aa1b93ba..150010a5ce 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -10,6 +10,9 @@ import java.util.ArrayList; +import static org.jsoup.internal.StringUtil.inSorted; +import static org.jsoup.parser.HtmlTreeBuilderState.Constants.*; + /** * The Tree Builder's current state. Each state embodies the processing for the state, and transitions to other states. */ @@ -51,7 +54,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { tb.insert(t.asStartTag()); tb.transition(BeforeHead); - } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().normalName(), "head", "body", "html", "br"))) { + } else if (t.isEndTag() && (inSorted(t.asEndTag().normalName(), BeforeHtmlToHead))) { return anythingElse(t, tb); } else if (t.isEndTag()) { tb.error(this); @@ -83,7 +86,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { Element head = tb.insert(t.asStartTag()); tb.setHeadElement(head); tb.transition(InHead); - } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().normalName(), "head", "body", "html", "br"))) { + } else if (t.isEndTag() && (inSorted(t.asEndTag().normalName(), BeforeHtmlToHead))) { tb.processStartTag("head"); return tb.process(t); } else if (t.isEndTag()) { @@ -114,9 +117,9 @@ boolean process(Token t, HtmlTreeBuilder tb) { String name = start.normalName(); if (name.equals("html")) { return InBody.process(t, tb); - } else if (StringUtil.in(name, "base", "basefont", "bgsound", "command", "link")) { + } else if (inSorted(name, InHeadEmpty)) { Element el = tb.insertEmpty(start); - // jsoup special: update base the frist time it is seen + // jsoup special: update base the first time it is seen if (name.equals("base") && el.hasAttr("href")) tb.maybeSetBaseUri(el); } else if (name.equals("meta")) { @@ -124,7 +127,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { // todo: charset switches } else if (name.equals("title")) { handleRcData(start, tb); - } else if (StringUtil.in(name, "noframes", "style")) { + } else if (inSorted(name, InHeadRaw)) { handleRawtext(start, tb); } else if (name.equals("noscript")) { // else if noscript && scripting flag = true: rawtext (jsoup doesn't run script, to handle as noscript) @@ -150,7 +153,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (name.equals("head")) { tb.pop(); tb.transition(AfterHead); - } else if (StringUtil.in(name, "body", "html", "br")) { + } else if (inSorted(name, Constants.InHeadEnd)) { return anythingElse(t, tb); } else { tb.error(this); @@ -177,12 +180,12 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isEndTag() && t.asEndTag().normalName().equals("noscript")) { tb.pop(); tb.transition(InHead); - } else if (isWhitespace(t) || t.isComment() || (t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), - "basefont", "bgsound", "link", "meta", "noframes", "style"))) { + } else if (isWhitespace(t) || t.isComment() || (t.isStartTag() && inSorted(t.asStartTag().normalName(), + InHeadNoScriptHead))) { return tb.process(t, InHead); } else if (t.isEndTag() && t.asEndTag().normalName().equals("br")) { return anythingElse(t, tb); - } else if ((t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "head", "noscript")) || t.isEndTag()) { + } else if ((t.isStartTag() && inSorted(t.asStartTag().normalName(), InHeadNoscriptIgnore)) || t.isEndTag()) { tb.error(this); return false; } else { @@ -217,7 +220,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (name.equals("frameset")) { tb.insert(startTag); tb.transition(InFrameset); - } else if (StringUtil.in(name, "base", "basefont", "bgsound", "link", "meta", "noframes", "script", "style", "title")) { + } else if (inSorted(name, InBodyStartToHead)) { tb.error(this); Element head = tb.getHeadElement(); tb.push(head); @@ -230,7 +233,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { anythingElse(t, tb); } } else if (t.isEndTag()) { - if (StringUtil.in(t.asEndTag().normalName(), "body", "html")) { + if (inSorted(t.asEndTag().normalName(), AfterHeadBody)) { anythingElse(t, tb); } else { tb.error(this); @@ -324,7 +327,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.processEndTag("li"); break; } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) + if (tb.isSpecial(el) && !inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; } if (tb.inButtonScope("p")) { @@ -464,7 +467,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { // input Attributes inputAttribs = new Attributes(); for (Attribute attr : startTag.attributes) { - if (!StringUtil.inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) + if (!inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) inputAttribs.put(attr); } inputAttribs.put("name", "isindex"); @@ -529,7 +532,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } - if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { + if (inSorted(tb.currentElement().normalName(), Constants.Headings)) { tb.error(this); tb.pop(); } @@ -552,11 +555,11 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { stack = tb.getStack(); for (int i = stack.size() - 1; i > 0; i--) { el = stack.get(i); - if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { + if (inSorted(el.normalName(), Constants.DdDt)) { tb.processEndTag(el.normalName()); break; } - if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) + if (tb.isSpecial(el) && !inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; } if (tb.inButtonScope("p")) { @@ -587,29 +590,29 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { break; default: // todo - bring scan groups in if desired - if (StringUtil.inSorted(name, Constants.InBodyStartEmptyFormatters)) { + if (inSorted(name, Constants.InBodyStartEmptyFormatters)) { tb.reconstructFormattingElements(); tb.insertEmpty(startTag); tb.framesetOk(false); - } else if (StringUtil.inSorted(name, Constants.InBodyStartPClosers)) { + } else if (inSorted(name, Constants.InBodyStartPClosers)) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { + } else if (inSorted(name, Constants.InBodyStartToHead)) { return tb.process(t, InHead); - } else if (StringUtil.inSorted(name, Constants.Formatters)) { + } else if (inSorted(name, Constants.Formatters)) { tb.reconstructFormattingElements(); el = tb.insert(startTag); tb.pushActiveFormattingElements(el); - } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { + } else if (inSorted(name, Constants.InBodyStartApplets)) { tb.reconstructFormattingElements(); tb.insert(startTag); tb.insertMarkerToFormattingElements(); tb.framesetOk(false); - } else if (StringUtil.inSorted(name, Constants.InBodyStartMedia)) { + } else if (inSorted(name, Constants.InBodyStartMedia)) { tb.insertEmpty(startTag); - } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { + } else if (inSorted(name, Constants.InBodyStartDrop)) { tb.error(this); return false; } else { @@ -714,9 +717,9 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; default: // todo - move rest to switch if desired - if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { + if (inSorted(name, Constants.InBodyEndAdoptionFormatters)) { return inBodyEndTagAdoption(t, tb); - } else if (StringUtil.inSorted(name, Constants.InBodyEndClosers)) { + } else if (inSorted(name, Constants.InBodyEndClosers)) { if (!tb.inScope(name)) { // nothing to close tb.error(this); @@ -727,7 +730,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { tb.error(this); tb.popStackToClose(name); } - } else if (StringUtil.inSorted(name, Constants.InBodyStartApplets)) { + } else if (inSorted(name, Constants.InBodyStartApplets)) { if (!tb.inScope("name")) { if (!tb.inScope(name)) { tb.error(this); @@ -841,7 +844,7 @@ else if (!tb.onStack(formatEl)) { lastNode = node; } - if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { + if (inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { if (lastNode.parent() != null) lastNode.remove(); tb.insertInFosterParent(lastNode); @@ -913,11 +916,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (name.equals("col")) { tb.processStartTag("colgroup"); return tb.process(t); - } else if (StringUtil.in(name, "tbody", "tfoot", "thead")) { + } else if (inSorted(name, InTableToBody)) { tb.clearStackToTableContext(); tb.insert(startTag); tb.transition(InTableBody); - } else if (StringUtil.in(name, "td", "th", "tr")) { + } else if (inSorted(name, InTableAddBody)) { tb.processStartTag("tbody"); return tb.process(t); } else if (name.equals("table")) { @@ -925,7 +928,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean processed = tb.processEndTag("table"); if (processed) // only ignored if in fragment return tb.process(t); - } else if (StringUtil.in(name, "style", "script")) { + } else if (inSorted(name, InTableToHead)) { return tb.process(t, InHead); } else if (name.equals("input")) { if (!startTag.attributes.get("type").equalsIgnoreCase("hidden")) { @@ -956,8 +959,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.popStackToClose("table"); } tb.resetInsertionMode(); - } else if (StringUtil.in(name, - "body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr")) { + } else if (inSorted(name, InTableEndErr)) { tb.error(this); return false; } else { @@ -975,7 +977,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.error(this); boolean processed; - if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { + if (inSorted(tb.currentElement().normalName(), InTableFoster)) { tb.setFosterInserts(true); processed = tb.process(t, InBody); tb.setFosterInserts(false); @@ -1001,7 +1003,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (!isWhitespace(character)) { // InTable anything else section: tb.error(this); - if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { + if (inSorted(tb.currentElement().normalName(), InTableFoster)) { tb.setFosterInserts(true); tb.process(new Token.Character().data(character), InBody); tb.setFosterInserts(false); @@ -1036,16 +1038,14 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.transition(InTable); } } else if (( - t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), - "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr") || + t.isStartTag() && inSorted(t.asStartTag().normalName(), InCellCol) || t.isEndTag() && t.asEndTag().normalName().equals("table")) ) { tb.error(this); boolean processed = tb.processEndTag("caption"); if (processed) return tb.process(t); - } else if (t.isEndTag() && StringUtil.in(t.asEndTag().normalName(), - "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr")) { + } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(), InCaptionIgnore)) { tb.error(this); return false; } else { @@ -1122,11 +1122,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.clearStackToTableBodyContext(); tb.insert(startTag); tb.transition(InRow); - } else if (StringUtil.in(name, "th", "td")) { + } else if (inSorted(name, InCellNames)) { tb.error(this); tb.processStartTag("tr"); return tb.process(startTag); - } else if (StringUtil.in(name, "caption", "col", "colgroup", "tbody", "tfoot", "thead")) { + } else if (inSorted(name, InTableBodyExit)) { return exitTableBody(t, tb); } else return anythingElse(t, tb); @@ -1134,7 +1134,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { case EndTag: Token.EndTag endTag = t.asEndTag(); name = endTag.normalName(); - if (StringUtil.in(name, "tbody", "tfoot", "thead")) { + if (inSorted(name, InTableEndIgnore)) { if (!tb.inTableScope(name)) { tb.error(this); return false; @@ -1145,7 +1145,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } } else if (name.equals("table")) { return exitTableBody(t, tb); - } else if (StringUtil.in(name, "body", "caption", "col", "colgroup", "html", "td", "th", "tr")) { + } else if (inSorted(name, InTableBodyEndIgnore)) { tb.error(this); return false; } else @@ -1180,12 +1180,12 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (name.equals("template")) { tb.insert(startTag); - } else if (StringUtil.in(name, "th", "td")) { + } else if (inSorted(name, InCellNames)) { tb.clearStackToTableRowContext(); tb.insert(startTag); tb.transition(InCell); tb.insertMarkerToFormattingElements(); - } else if (StringUtil.in(name, "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr")) { + } else if (inSorted(name, InRowMissing)) { return handleMissingTr(t, tb); } else { return anythingElse(t, tb); @@ -1204,14 +1204,14 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.transition(InTableBody); } else if (name.equals("table")) { return handleMissingTr(t, tb); - } else if (StringUtil.in(name, "tbody", "tfoot", "thead")) { + } else if (inSorted(name, InTableToBody)) { if (!tb.inTableScope(name)) { tb.error(this); return false; } tb.processEndTag("tr"); return tb.process(t); - } else if (StringUtil.in(name, "body", "caption", "col", "colgroup", "html", "td", "th")) { + } else if (inSorted(name, InRowIgnore)) { tb.error(this); return false; } else { @@ -1241,7 +1241,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { Token.EndTag endTag = t.asEndTag(); String name = endTag.normalName(); - if (StringUtil.inSorted(name, Constants.InCellNames)) { + if (inSorted(name, Constants.InCellNames)) { if (!tb.inTableScope(name)) { tb.error(this); tb.transition(InRow); // might not be in scope if empty: <td /> and processing fake end tag @@ -1253,10 +1253,10 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); tb.transition(InRow); - } else if (StringUtil.inSorted(name, Constants.InCellBody)) { + } else if (inSorted(name, Constants.InCellBody)) { tb.error(this); return false; - } else if (StringUtil.inSorted(name, Constants.InCellTable)) { + } else if (inSorted(name, Constants.InCellTable)) { if (!tb.inTableScope(name)) { tb.error(this); return false; @@ -1267,7 +1267,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return anythingElse(t, tb); } } else if (t.isStartTag() && - StringUtil.inSorted(t.asStartTag().normalName(), Constants.InCellCol)) { + inSorted(t.asStartTag().normalName(), Constants.InCellCol)) { if (!(tb.inTableScope("td") || tb.inTableScope("th"))) { tb.error(this); return false; @@ -1327,7 +1327,7 @@ else if (name.equals("option")) { } else if (name.equals("select")) { tb.error(this); return tb.processEndTag("select"); - } else if (StringUtil.in(name, "input", "keygen", "textarea")) { + } else if (inSorted(name, InSelectEnd)) { tb.error(this); if (!tb.inSelectScope("select")) return false; // frag @@ -1387,11 +1387,11 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { }, InSelectInTable { boolean process(Token t, HtmlTreeBuilder tb) { - if (t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { + if (t.isStartTag() && inSorted(t.asStartTag().normalName(), InSelecTableEnd)) { tb.error(this); tb.processEndTag("select"); return tb.process(t); - } else if (t.isEndTag() && StringUtil.in(t.asEndTag().normalName(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { + } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(),InSelecTableEnd )) { tb.error(this); if (tb.inTableScope(t.asEndTag().normalName())) { tb.processEndTag("select"); @@ -1573,16 +1573,19 @@ private static void handleRawtext(Token.StartTag startTag, HtmlTreeBuilder tb) { tb.insert(startTag); } - // lists of tags to search through. A little harder to read here, but causes less GC than dynamic varargs. - // was contributing around 10% of parse GC load. - // must make sure these are sorted, as used in findSorted. MUST update HtmlTreebuilderStateTest if more arrays added. + // lists of tags to search through static final class Constants { + static final String[] InHeadEmpty = new String[]{"base", "basefont", "bgsound", "command", "link"}; + static final String[] InHeadRaw = new String[]{"noframes", "style"}; + static final String[] InHeadEnd = new String[]{"body", "br", "html"}; + static final String[] AfterHeadBody = new String[]{"body", "html"}; + static final String[] BeforeHtmlToHead = new String[]{"body", "br", "head", "html", }; + static final String[] InHeadNoScriptHead = new String[]{"basefont", "bgsound", "link", "meta", "noframes", "style"}; static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"}; static final String[] InBodyStartPClosers = new String[]{"address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", "p", "section", "summary", "ul"}; static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; - static final String[] InBodyStartPreListing = new String[]{"listing", "pre"}; static final String[] InBodyStartLiBreakers = new String[]{"address", "div", "p"}; static final String[] DdDt = new String[]{"dd", "dt"}; static final String[] Formatters = new String[]{"b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u"}; @@ -1590,17 +1593,29 @@ static final class Constants { static final String[] InBodyStartEmptyFormatters = new String[]{"area", "br", "embed", "img", "keygen", "wbr"}; static final String[] InBodyStartMedia = new String[]{"param", "source", "track"}; static final String[] InBodyStartInputAttribs = new String[]{"action", "name", "prompt"}; - static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; - static final String[] InBodyStartRuby = new String[]{"rp", "rt"}; static final String[] InBodyStartDrop = new String[]{"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"}; static final String[] InBodyEndClosers = new String[]{"address", "article", "aside", "blockquote", "button", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "menu", "nav", "ol", "pre", "section", "summary", "ul"}; static final String[] InBodyEndAdoptionFormatters = new String[]{"a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u"}; static final String[] InBodyEndTableFosters = new String[]{"table", "tbody", "tfoot", "thead", "tr"}; + static final String[] InTableToBody = new String[]{"tbody", "tfoot", "thead"}; + static final String[] InTableAddBody = new String[]{"td", "th", "tr"}; + static final String[] InTableToHead = new String[]{"script", "style"}; static final String[] InCellNames = new String[]{"td", "th"}; static final String[] InCellBody = new String[]{"body", "caption", "col", "colgroup", "html"}; static final String[] InCellTable = new String[]{ "table", "tbody", "tfoot", "thead", "tr"}; static final String[] InCellCol = new String[]{"caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"}; + static final String[] InTableEndErr = new String[]{"body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr"}; + static final String[] InTableFoster = new String[]{"table", "tbody", "tfoot", "thead", "tr"}; + static final String[] InTableBodyExit = new String[]{"caption", "col", "colgroup", "tbody", "tfoot", "thead"}; + static final String[] InTableBodyEndIgnore = new String[]{"body", "caption", "col", "colgroup", "html", "td", "th", "tr"}; + static final String[] InRowMissing = new String[]{"caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr"}; + static final String[] InRowIgnore = new String[]{"body", "caption", "col", "colgroup", "html", "td", "th"}; + static final String[] InSelectEnd = new String[]{"input", "keygen", "textarea"}; + static final String[] InSelecTableEnd = new String[]{"caption", "table", "tbody", "td", "tfoot", "th", "thead", "tr"}; + static final String[] InTableEndIgnore = new String[]{"tbody", "tfoot", "thead"}; + static final String[] InHeadNoscriptIgnore = new String[]{"head", "noscript"}; + static final String[] InCaptionIgnore = new String[]{"body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr"}; } } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 6cbf1e0ef0..1836e409b2 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -3,42 +3,47 @@ import org.jsoup.parser.HtmlTreeBuilderState.Constants; import org.junit.Test; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; public class HtmlTreeBuilderStateTest { - @Test - public void ensureArraysAreSorted() { - String[][] arrays = { - Constants.InBodyStartToHead, - Constants.InBodyStartPClosers, - Constants.Headings, - Constants.InBodyStartPreListing, - Constants.InBodyStartLiBreakers, - Constants.DdDt, - Constants.Formatters, - Constants.InBodyStartApplets, - Constants.InBodyStartEmptyFormatters, - Constants.InBodyStartMedia, - Constants.InBodyStartInputAttribs, - Constants.InBodyStartOptions, - Constants.InBodyStartRuby, - Constants.InBodyStartDrop, - Constants.InBodyEndClosers, - Constants.InBodyEndAdoptionFormatters, - Constants.InBodyEndTableFosters, - Constants.InCellNames, - Constants.InCellBody, - Constants.InCellTable, - Constants.InCellCol, - }; - - for (String[] array : arrays) { - String[] copy = Arrays.copyOf(array, array.length); + static List<Object[]> findArrays(Class<?> constantClass) { + ArrayList<Object[]> array = new ArrayList<>(); + + Field[] fields = constantClass.getDeclaredFields(); + + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getType().isArray()) { + try { + array.add((Object[]) field.get(null)); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + } + + return array; + } + + static void ensureSorted(List<Object[]> constants) { + for (Object[] array : constants) { + Object[] copy = Arrays.copyOf(array, array.length); Arrays.sort(array); assertArrayEquals(array, copy); } } + @Test + public void ensureArraysAreSorted() { + List<Object[]> constants = findArrays(Constants.class); + ensureSorted(constants); + assertEquals(38, constants.size()); + } + } From 9675a92b41593244d6d3ecd99425aeb19e2a6219 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 29 Feb 2020 16:10:16 -0800 Subject: [PATCH 422/774] Preserve whitespace in nodes before <head> --- CHANGES | 2 ++ src/main/java/org/jsoup/nodes/TextNode.java | 8 ++++--- .../org/jsoup/parser/HtmlTreeBuilder.java | 13 ++++++---- .../jsoup/parser/HtmlTreeBuilderState.java | 24 ++++++++++++------- .../java/org/jsoup/helper/W3CDomTest.java | 4 ++-- .../java/org/jsoup/parser/HtmlParserTest.java | 17 +++++++++++++ .../org/jsoup/parser/XmlTreeBuilderTest.java | 2 +- 7 files changed, 51 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index ac44b86c97..fff3018a1d 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,8 @@ jsoup changelog * Improvement: added Elements#forms(), Elements#textNodes(), Elements#dataNodes(), and Elements#comments(), as a convenient way to get access to these node types directly from an element selection. + * Improvement: preserve whitespace before html and head tag, if pretty-printing is off. + * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup> <https://github.com/jhy/jsoup/issues/1313> diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 23ad9e7252..45794a6aa3 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -81,11 +81,13 @@ public TextNode splitText(int offset) { } void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { - if (out.prettyPrint() && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) + final boolean prettyPrint = out.prettyPrint(); + if (prettyPrint && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) indent(accum, depth, out); - boolean normaliseWhite = out.prettyPrint() && !Element.preserveWhitespace(parent()); - Entities.escape(accum, coreValue(), out, false, normaliseWhite, false); + final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); + final boolean stripWhite = prettyPrint && parentNode instanceof Document; + Entities.escape(accum, coreValue(), out, false, normaliseWhite, stripWhite); } void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 6b35a58d75..b879de3eb5 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -261,7 +261,9 @@ void insert(Token.Comment commentToken) { void insert(Token.Character characterToken) { final Node node; - final Element el = currentElement(); + Element el = currentElement(); + if (el == null) + el = doc; // allows for whitespace to be inserted into the doc root object (not on the stack) final String tagName = el.normalName(); final String data = characterToken.getData(); @@ -338,13 +340,14 @@ boolean removeFromStack(Element el) { return false; } - void popStackToClose(String elName) { + Element popStackToClose(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { - Element next = stack.get(pos); + Element el = stack.get(pos); stack.remove(pos); - if (next.normalName().equals(elName)) - break; + if (el.normalName().equals(elName)) + return el; } + return null; } // elnames is sorted, comes from Constants diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 150010a5ce..21c8f64c23 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -20,7 +20,7 @@ enum HtmlTreeBuilderState { Initial { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { - return true; // ignore whitespace + return true; // ignore whitespace until we get the first content } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { @@ -50,7 +50,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isComment()) { tb.insert(t.asComment()); } else if (isWhitespace(t)) { - return true; // ignore whitespace + tb.insert(t.asCharacter()); // out of spec - include whitespace } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { tb.insert(t.asStartTag()); tb.transition(BeforeHead); @@ -74,7 +74,7 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { BeforeHead { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { - return true; + tb.insert(t.asCharacter()); // out of spec - include whitespace } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { @@ -102,7 +102,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { InHead { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { - tb.insert(t.asCharacter()); + tb.insert(t.asCharacter()); // out of spec - include whitespace return true; } switch (t.type) { @@ -1406,7 +1406,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { AfterBody { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { - return tb.process(t, InBody); + tb.insert(t.asCharacter()); // out of spec - include whitespace. spec would move into body } else if (t.isComment()) { tb.insert(t.asComment()); // into html node } else if (t.isDoctype()) { @@ -1507,9 +1507,17 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isComment()) { tb.insert(t.asComment()); - } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { + } else if (t.isDoctype() || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { return tb.process(t, InBody); - } else if (t.isEOF()) { + } else if (isWhitespace(t)) { + // allows space after </html>, and put the body back on stack to allow subsequent tags if any + // todo - might be better for </body> and </html> to close them, allow trailing space, and then reparent + // that space into body if other tags get re-added. but that's overkill for now + Element html = tb.popStackToClose("html"); + tb.insert(t.asCharacter()); + tb.stack.add(html); + tb.stack.add(html.selectFirst("body")); + }else if (t.isEOF()) { // nice work chuck } else { tb.error(this); @@ -1550,7 +1558,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { private static boolean isWhitespace(Token t) { if (t.isCharacter()) { String data = t.asCharacter().getData(); - return isWhitespace(data); + return StringUtil.isBlank(data); } return false; } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 45bb318e63..30b5e3680b 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -134,12 +134,12 @@ public void namespacePreservation() throws IOException { assertEquals("html", htmlEl.getNodeName()); // inherits default namespace - Node head = htmlEl.getFirstChild(); + Node head = htmlEl.getFirstChild().getNextSibling(); assertEquals("http://www.w3.org/1999/xhtml", head.getNamespaceURI()); assertEquals("head", head.getLocalName()); assertEquals("head", head.getNodeName()); - Node epubTitle = htmlEl.getChildNodes().item(2).getChildNodes().item(3); + Node epubTitle = htmlEl.getChildNodes().item(3).getChildNodes().item(3); assertEquals("Check", epubTitle.getTextContent()); assertEquals("http://www.idpf.org/2007/ops", epubTitle.getNamespaceURI()); assertEquals("title", epubTitle.getLocalName()); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 02c108d61d..00e4f714a4 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1355,4 +1355,21 @@ public void testUNewlines() { doc = Jsoup.parse(html, "", Parser.htmlParser().settings(preserveCase)); assertEquals("YES YES", doc.selectFirst("textarea").val()); } + + @Test public void preserveWhitespaceInHead() { + String html = "\n<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n</body>\n</html>\n"; + Document doc = Jsoup.parse(html); + doc.outputSettings().prettyPrint(false); + System.out.println(doc.outerHtml()); + assertEquals("<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n\n</body></html>\n", doc.outerHtml()); + } + + @Test public void handleContentAfterBody() { + String html = "<body>One</body> <p>Hello!</p></html> <p>There</p>"; + // todo - ideally would move that space afer /html to the body when the There <p> is seen + Document doc = Jsoup.parse(html); + doc.outputSettings().prettyPrint(false); + System.out.println(doc.outerHtml()); + assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One <p>Hello!</p><p>There</p></body></html> ", doc.outerHtml()); + } } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 490dee0ea5..79efc34bb4 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -143,7 +143,7 @@ public void testDetectCharsetEncodingDeclaration() throws IOException, URISyntax InputStream inStream = new FileInputStream(xmlFile); Document doc = Jsoup.parse(inStream, null, "http://example.com/", Parser.xmlParser()); assertEquals("ISO-8859-1", doc.charset().name()); - assertEquals("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?> <data>äöåéü</data>", + assertEquals("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><data>äöåéü</data>", TextUtil.stripNewlines(doc.html())); } From fb9aa17b42dc62a087634788b93d9cba9d91d203 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 29 Feb 2020 17:17:53 -0800 Subject: [PATCH 423/774] Changes for #1327 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index fff3018a1d..89ef2c8b9b 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,9 @@ jsoup changelog 2. Only track the baseUri in an element when it is set via DOM to a new value for a given tree 3. After parsing, do not retain the input character reader (and associated buffers) in the Document#parser + * Improvement: substantial parse speed improvements vs 1.12.x (bringing back to par with previous releases). + <https://github.com/jhy/jsoup/issues/1327> + * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline * Improvement: added Attributes#hasDeclaredValueForKey(key) and Attribute#hasDeclaredValueForKeyIgnoreCase(), to check From dcf3c8ba5a0963260999f463ee80b6a787928bb9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 29 Feb 2020 17:21:15 -0800 Subject: [PATCH 424/774] [maven-release-plugin] prepare release jsoup-1.13.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 38ffa842c0..8dc1c1bc4a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.13.1-SNAPSHOT</version> + <version>1.13.1</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.13.1</tag> </scm> <organization> <name>Jonathan Hedley</name> From 1330299a3d763b05f1a29296328c1105a1f791c9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 29 Feb 2020 17:21:23 -0800 Subject: [PATCH 425/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8dc1c1bc4a..7e4dea7f73 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.13.1</version> + <version>1.13.2-SNAPSHOT</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>jsoup-1.13.1</tag> + <tag>HEAD</tag> </scm> <organization> <name>Jonathan Hedley</name> From e807847d7c56c2014df8a3945d94585cccc13ddd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 1 Mar 2020 11:00:13 -0800 Subject: [PATCH 426/774] Release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 89ef2c8b9b..70210bbe2e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.13.1 [PENDING] +*** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. <https://github.com/jhy/jsoup/issues/1326> From 5136929fea4c786ddccc040da84905a3546e92ba Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 1 Mar 2020 11:09:27 -0800 Subject: [PATCH 427/774] Testcase to show #1292 works as-is. --- .../java/org/jsoup/select/SelectorTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index bb70d52d3b..7720b254c0 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -942,4 +942,21 @@ public void xmlWildcardNamespaceTest() { assertEquals("1111", select.get(0).text()); assertEquals("2222", select.get(1).text()); } + + @Test + public void childElements() { + // https://github.com/jhy/jsoup/issues/1292 + String html = "<body><span id=1>One <span id=2>Two</span></span></body>"; + Document doc = Jsoup.parse(html); + + Element outer = doc.selectFirst("span"); + Element span = outer.selectFirst("span"); + Element inner = outer.selectFirst("* span"); + + assertEquals("1", outer.id()); + assertEquals("1", span.id()); + assertEquals("2", inner.id()); + assertEquals(outer, span); + assertNotEquals(outer, inner); + } } From 34715b3858bcdbae76d0bb31921ef6a806632a86 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 1 Mar 2020 12:19:04 -0800 Subject: [PATCH 428/774] Added support for loading gzipped HTML files. Mostly this is so I can compress the test HTML files in the repo, so that GitHub stops reporting jsoup as a HTML project. --- CHANGES | 3 + src/main/java/org/jsoup/helper/DataUtil.java | 19 +- .../java/org/jsoup/helper/DataUtilTest.java | 31 + .../java/org/jsoup/helper/W3CDomTest.java | 4 +- .../org/jsoup/integration/ConnectTest.java | 6 +- .../java/org/jsoup/integration/ParseTest.java | 34 +- .../java/org/jsoup/nodes/DocumentTest.java | 4 +- .../java/org/jsoup/parser/HtmlParserTest.java | 4 +- src/test/resources/bomtests/bom_utf8.html.gz | Bin 0 -> 228 bytes .../htmltests/character-reader-buffer.html | 244 -- .../htmltests/character-reader-buffer.html.gz | Bin 0 -> 282 bytes .../resources/htmltests/fake-gzip.html.gz | Bin 0 -> 101 bytes src/test/resources/htmltests/google-ipod.html | 10 - .../resources/htmltests/google-ipod.html.gz | Bin 0 -> 12212 bytes src/test/resources/htmltests/gzip.html | 4 + src/test/resources/htmltests/gzip.html.gz | Bin 0 -> 85 bytes src/test/resources/htmltests/gzip.html.z | Bin 0 -> 85 bytes .../resources/htmltests/news-com-au-home.html | 3039 ----------------- .../htmltests/news-com-au-home.html.gz | Bin 0 -> 31720 bytes .../resources/htmltests/nyt-article-1.html | 948 ----- .../resources/htmltests/nyt-article-1.html.gz | Bin 0 -> 15694 bytes .../htmltests/smh-biz-article-1.html | 1633 --------- .../htmltests/smh-biz-article-1.html.gz | Bin 0 -> 20383 bytes src/test/resources/htmltests/xwiki-1324.html | 1015 ------ .../resources/htmltests/xwiki-1324.html.gz | Bin 0 -> 11041 bytes src/test/resources/htmltests/xwiki-edit.html | 1015 ------ .../resources/htmltests/xwiki-edit.html.gz | Bin 0 -> 13934 bytes .../resources/htmltests/yahoo-article-1.html | 2043 ----------- .../htmltests/yahoo-article-1.html.gz | Bin 0 -> 38736 bytes src/test/resources/htmltests/yahoo-jp.html | 602 ---- src/test/resources/htmltests/yahoo-jp.html.gz | Bin 0 -> 24669 bytes 31 files changed, 84 insertions(+), 10574 deletions(-) create mode 100644 src/test/resources/bomtests/bom_utf8.html.gz delete mode 100644 src/test/resources/htmltests/character-reader-buffer.html create mode 100644 src/test/resources/htmltests/character-reader-buffer.html.gz create mode 100644 src/test/resources/htmltests/fake-gzip.html.gz delete mode 100644 src/test/resources/htmltests/google-ipod.html create mode 100644 src/test/resources/htmltests/google-ipod.html.gz create mode 100644 src/test/resources/htmltests/gzip.html create mode 100644 src/test/resources/htmltests/gzip.html.gz create mode 100644 src/test/resources/htmltests/gzip.html.z delete mode 100644 src/test/resources/htmltests/news-com-au-home.html create mode 100644 src/test/resources/htmltests/news-com-au-home.html.gz delete mode 100644 src/test/resources/htmltests/nyt-article-1.html create mode 100644 src/test/resources/htmltests/nyt-article-1.html.gz delete mode 100644 src/test/resources/htmltests/smh-biz-article-1.html create mode 100644 src/test/resources/htmltests/smh-biz-article-1.html.gz delete mode 100644 src/test/resources/htmltests/xwiki-1324.html create mode 100644 src/test/resources/htmltests/xwiki-1324.html.gz delete mode 100644 src/test/resources/htmltests/xwiki-edit.html create mode 100644 src/test/resources/htmltests/xwiki-edit.html.gz delete mode 100644 src/test/resources/htmltests/yahoo-article-1.html create mode 100644 src/test/resources/htmltests/yahoo-article-1.html.gz delete mode 100644 src/test/resources/htmltests/yahoo-jp.html create mode 100644 src/test/resources/htmltests/yahoo-jp.html.gz diff --git a/CHANGES b/CHANGES index 70210bbe2e..31e1bfa7e2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,8 @@ jsoup changelog +*** Release 1.13.2 [PENDING] + * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 568df983f5..c4d94bbe6a 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -2,6 +2,7 @@ import org.jsoup.UncheckedIOException; import org.jsoup.internal.ConstrainableInputStream; +import org.jsoup.internal.Normalizer; import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; @@ -28,6 +29,7 @@ import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; /** * Internal static utilities for handling data. @@ -45,15 +47,26 @@ public final class DataUtil { private DataUtil() {} /** - * Loads a file to a Document. + * Loads and parses a file to a Document. Files that are compressed with gzip (and end in {@code .gz} or {@code .z}) + * are supported in addition to uncompressed files. + * * @param in file to load - * @param charsetName character set of input + * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in + * the file will always override this setting. * @param baseUri base URI of document, to resolve relative links against * @return Document * @throws IOException on IO error */ public static Document load(File in, String charsetName, String baseUri) throws IOException { - return parseInputStream(new FileInputStream(in), charsetName, baseUri, Parser.htmlParser()); + InputStream stream = new FileInputStream(in); + String name = Normalizer.lowerCase(in.getName()); + if (name.endsWith(".gz") || name.endsWith(".z")) { + // unfortunately file input streams don't support marks (why not?), so we will close and reopen after read + boolean zipped = (stream.read() == 0x1f && stream.read() == 0x8b); // gzip magic bytes + stream.close(); + stream = zipped ? new GZIPInputStream(new FileInputStream(in)) : new FileInputStream(in); + } + return parseInputStream(stream, charsetName, baseUri, Parser.htmlParser()); } /** diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index c1f45d4185..32ecd5fa62 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -165,6 +165,14 @@ public void supportsUTF8BOM() throws IOException { assertEquals("OK", doc.head().select("title").text()); } + @Test + public void supportsZippedUTF8BOM() throws IOException { + File in = getFile("/bomtests/bom_utf8.html.gz"); + Document doc = Jsoup.parse(in, null, "http://example.com"); + assertEquals("OK", doc.head().select("title").text()); + assertEquals("There is a UTF8 BOM at the top (before the XML decl). If not read correctly, will look like a non-joining space.", doc.body().text()); + } + @Test public void supportsXmlCharsetDeclaration() throws IOException { String encoding = "iso-8859-1"; @@ -177,4 +185,27 @@ public void supportsXmlCharsetDeclaration() throws IOException { Document doc = Jsoup.parse(soup, null, ""); assertEquals("Hellö Wörld!", doc.body().text()); } + + + @Test public void lLoadsGzipFile() throws IOException { + File in = getFile("/htmltests/gzip.html.gz"); + Document doc = Jsoup.parse(in, null); + assertEquals("Gzip test", doc.title()); + assertEquals("This is a gzipped HTML file.", doc.selectFirst("p").text()); + } + + @Test public void loadsZGzipFile() throws IOException { + // compressed on win, with z suffix + File in = getFile("/htmltests/gzip.html.z"); + Document doc = Jsoup.parse(in, null); + assertEquals("Gzip test", doc.title()); + assertEquals("This is a gzipped HTML file.", doc.selectFirst("p").text()); + } + + @Test public void handlesFakeGzipFile () throws IOException { + File in = getFile("/htmltests/fake-gzip.html.gz"); + Document doc = Jsoup.parse(in, null); + assertEquals("This is not gzipped", doc.title()); + assertEquals("And should still be readable.", doc.selectFirst("p").text()); + } } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 30b5e3680b..6b84e52457 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -84,7 +84,7 @@ public void simpleConversion() { @Test public void convertsGoogle() throws IOException { - File in = ParseTest.getFile("/htmltests/google-ipod.html"); + File in = ParseTest.getFile("/htmltests/google-ipod.html.gz"); org.jsoup.nodes.Document doc = Jsoup.parse(in, "UTF8"); W3CDom w3c = new W3CDom(); @@ -108,7 +108,7 @@ public void convertsGoogle() throws IOException { @Test public void convertsGoogleLocation() throws IOException { - File in = ParseTest.getFile("/htmltests/google-ipod.html"); + File in = ParseTest.getFile("/htmltests/google-ipod.html.gz"); org.jsoup.nodes.Document doc = Jsoup.parse(in, "UTF8"); W3CDom w3c = new W3CDom(); diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index a0193e22ab..822ee6bca2 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -233,7 +233,7 @@ public void doesPut() throws IOException { @Test public void postFiles() throws IOException { File thumb = ParseTest.getFile("/htmltests/thumb.jpg"); - File html = ParseTest.getFile("/htmltests/google-ipod.html"); + File html = ParseTest.getFile("/htmltests/google-ipod.html.gz"); Document res = Jsoup .connect(EchoServlet.Url) @@ -247,8 +247,8 @@ public void postFiles() throws IOException { assertEquals("application/octet-stream", ihVal("Part secondPart ContentType", res)); assertEquals("secondPart", ihVal("Part secondPart Name", res)); - assertEquals("google-ipod.html", ihVal("Part secondPart Filename", res)); - assertEquals("43963", ihVal("Part secondPart Size", res)); + assertEquals("google-ipod.html.gz", ihVal("Part secondPart Filename", res)); + assertEquals("12212", ihVal("Part secondPart Size", res)); assertEquals("image/jpeg", ihVal("Part firstPart ContentType", res)); assertEquals("firstPart", ihVal("Part firstPart Name", res)); diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index cfdd059947..313a091fd6 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -1,6 +1,7 @@ package org.jsoup.integration; import org.jsoup.Jsoup; +import org.jsoup.helper.DataUtil; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.parser.ParseErrorList; @@ -11,8 +12,10 @@ import java.io.*; import java.net.URISyntaxException; import java.net.URL; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.util.zip.GZIPInputStream; import static org.junit.Assert.*; @@ -25,7 +28,7 @@ public class ParseTest { @Test public void testSmhBizArticle() throws IOException { - File in = getFile("/htmltests/smh-biz-article-1.html"); + File in = getFile("/htmltests/smh-biz-article-1.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.smh.com.au/business/the-boards-next-fear-the-female-quota-20100106-lteq.html"); assertEquals("The board’s next fear: the female quota", @@ -40,7 +43,7 @@ public void testSmhBizArticle() throws IOException { @Test public void testNewsHomepage() throws IOException { - File in = getFile("/htmltests/news-com-au-home.html"); + File in = getFile("/htmltests/news-com-au-home.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.news.com.au/"); assertEquals("News.com.au | News from Australia and around the world online | NewsComAu", doc.title()); assertEquals("Brace yourself for Metro meltdown", doc.select(".id1225817868581 h4").text().trim()); @@ -58,7 +61,7 @@ public void testNewsHomepage() throws IOException { @Test public void testGoogleSearchIpod() throws IOException { - File in = getFile("/htmltests/google-ipod.html"); + File in = getFile("/htmltests/google-ipod.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.google.com/search?hl=en&q=ipod&aq=f&oq=&aqi=g10"); assertEquals("ipod - Google Search", doc.title()); Elements results = doc.select("h3.r > a"); @@ -72,7 +75,7 @@ public void testGoogleSearchIpod() throws IOException { @Test public void testYahooJp() throws IOException { - File in = getFile("/htmltests/yahoo-jp.html"); + File in = getFile("/htmltests/yahoo-jp.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.yahoo.co.jp/index.html"); // http charset is utf-8. assertEquals("Yahoo! JAPAN", doc.title()); Element a = doc.select("a[href=t/2322m2]").first(); @@ -150,7 +153,7 @@ public void testBrokenHtml5CharsetWithASingleDoubleQuote() throws IOException { @Test public void testNytArticle() throws IOException { // has tags like <nyt_text> - File in = getFile("/htmltests/nyt-article-1.html"); + File in = getFile("/htmltests/nyt-article-1.html.gz"); Document doc = Jsoup.parse(in, null, "http://www.nytimes.com/2010/07/26/business/global/26bp.html?hp"); Element headline = doc.select("nyt_headline[version=1.0]").first(); @@ -159,7 +162,7 @@ public void testNytArticle() throws IOException { @Test public void testYahooArticle() throws IOException { - File in = getFile("/htmltests/yahoo-article-1.html"); + File in = getFile("/htmltests/yahoo-article-1.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china"); Element p = doc.select("p:contains(Volt will be sold in the United States)").first(); assertEquals("In July, GM said its electric Chevrolet Volt will be sold in the United States at $41,000 -- $8,000 more than its nearest competitor, the Nissan Leaf.", p.text()); @@ -179,7 +182,7 @@ public void testLowercaseUtf8Charset() throws IOException { public void testXwiki() throws IOException { // https://github.com/jhy/jsoup/issues/1324 // this tests that when in CharacterReader we hit a buffer while marked, we preserve the mark when buffered up and can rewind - File in = getFile("/htmltests/xwiki-1324.html"); + File in = getFile("/htmltests/xwiki-1324.html.gz"); Document doc = Jsoup.parse(in, null, "https://localhost/"); assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); @@ -194,9 +197,9 @@ public void testXwikiExpanded() throws IOException { // https://github.com/jhy/jsoup/issues/1324 // this tests that if there is a huge illegal character reference, we can get through a buffer and rewind, and still catch that it's an invalid refence, // and the parse tree is correct. - File in = getFile("/htmltests/xwiki-edit.html"); + File in = getFile("/htmltests/xwiki-edit.html.gz"); Parser parser = Parser.htmlParser(); - Document doc = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://localhost/", parser.setTrackErrors(100)); + Document doc = Jsoup.parse(new GZIPInputStream(new FileInputStream(in)), "UTF-8", "https://localhost/", parser.setTrackErrors(100)); ParseErrorList errors = parser.getErrors(); assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); @@ -209,7 +212,7 @@ public void testXwikiExpanded() throws IOException { } @Test public void testWikiExpandedFromString() throws IOException { - File in = getFile("/htmltests/xwiki-edit.html"); + File in = getFile("/htmltests/xwiki-edit.html.gz"); String html = getFileAsString(in); Document doc = Jsoup.parse(html); assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); @@ -218,7 +221,7 @@ public void testXwikiExpanded() throws IOException { } @Test public void testWikiFromString() throws IOException { - File in = getFile("/htmltests/xwiki-1324.html"); + File in = getFile("/htmltests/xwiki-1324.html.gz"); String html = getFileAsString(in); Document doc = Jsoup.parse(html); assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); @@ -240,7 +243,14 @@ public static InputStream inputStreamFrom(String s) { } public static String getFileAsString(File file) throws IOException { - byte[] bytes = Files.readAllBytes(file.toPath()); + byte[] bytes; + if (file.getName().endsWith(".gz")) { + InputStream stream = new GZIPInputStream(new FileInputStream(file)); + ByteBuffer byteBuffer = DataUtil.readToByteBuffer(stream, 0); + bytes = byteBuffer.array(); + } else { + bytes = Files.readAllBytes(file.toPath()); + } return new String(bytes); } diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 98bd1b272c..c496a4376f 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -100,13 +100,13 @@ public class DocumentTest { } @Test public void testLocation() throws IOException { - File in = ParseTest.getFile("/htmltests/yahoo-jp.html"); + File in = ParseTest.getFile("/htmltests/yahoo-jp.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.yahoo.co.jp/index.html"); String location = doc.location(); String baseUri = doc.baseUri(); assertEquals("http://www.yahoo.co.jp/index.html",location); assertEquals("http://www.yahoo.co.jp/_ylh=X3oDMTB0NWxnaGxsBF9TAzIwNzcyOTYyNjUEdGlkAzEyBHRtcGwDZ2Ex/",baseUri); - in = ParseTest.getFile("/htmltests/nyt-article-1.html"); + in = ParseTest.getFile("/htmltests/nyt-article-1.html.gz"); doc = Jsoup.parse(in, null, "http://www.nytimes.com/2010/07/26/business/global/26bp.html?hp"); location = doc.location(); baseUri = doc.baseUri(); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 00e4f714a4..c9a3df7432 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1227,7 +1227,7 @@ public void testInvalidTableContents() throws IOException { } @Test public void characterReaderBuffer() throws IOException { - File in = ParseTest.getFile("/htmltests/character-reader-buffer.html"); + File in = ParseTest.getFile("/htmltests/character-reader-buffer.html.gz"); Document doc = Jsoup.parse(in, "UTF-8"); String expectedHref = "http://www.domain.com/path?param_one=value&param_two=value"; @@ -1360,7 +1360,6 @@ public void testUNewlines() { String html = "\n<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n</body>\n</html>\n"; Document doc = Jsoup.parse(html); doc.outputSettings().prettyPrint(false); - System.out.println(doc.outerHtml()); assertEquals("<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n\n</body></html>\n", doc.outerHtml()); } @@ -1369,7 +1368,6 @@ public void testUNewlines() { // todo - ideally would move that space afer /html to the body when the There <p> is seen Document doc = Jsoup.parse(html); doc.outputSettings().prettyPrint(false); - System.out.println(doc.outerHtml()); assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One <p>Hello!</p><p>There</p></body></html> ", doc.outerHtml()); } } diff --git a/src/test/resources/bomtests/bom_utf8.html.gz b/src/test/resources/bomtests/bom_utf8.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e00c00c0a3bb268dbb09db8690bd4f836c91e52e GIT binary patch literal 228 zcmV<A02}`wiwFqPP1sxj17dG&Uv+e5I4)>(ZEOHFj=fI9KoEp`KgHk*B;tfF#l?{l zK#CwTg^@_<oY(Q;^=))se)4P-JQA<Kft6}zw5yr#=j+=nKYPYIg~5qmq&L|t1$isA z^M^(Hrp;uYF7w1(+!pI{_jHF=dye@mRMmL`7<I~&7Y}ASy2MP!o7qc!%9FCCKyU*p zJeT)#tcoovg|-BhBQE#UNPPIqFWX1dWcez?rhyklh)u8(0$JtLH9j0OSfm5Hj$)-3 eKY11BBQ%C%WhsmM<8b8oNBspI0~}K90000hWNQZi literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/character-reader-buffer.html b/src/test/resources/htmltests/character-reader-buffer.html deleted file mode 100644 index c367a90652..0000000000 --- a/src/test/resources/htmltests/character-reader-buffer.html +++ /dev/null @@ -1,244 +0,0 @@ -<html> -<head> -<title>Test</title> - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> -<body> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ---------------------------------------------------------------------------------------------------<br/> ------------------------------------------------<br/> -<a href="http://www.domain.com/path?param_one=value&param_two=value">Link one</a> -<a href="http://www.domain.com/path?param_one=value&param_two=value">Link two</a> -</body> -</html> \ No newline at end of file diff --git a/src/test/resources/htmltests/character-reader-buffer.html.gz b/src/test/resources/htmltests/character-reader-buffer.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..a5d096b9ed695a8ae0433dce68f64e670694d023 GIT binary patch literal 282 zcmb2|=HOtyawCR;IXNS-C^5MtwMe%pH8BN9C6%V7r55RBl;q|xyuEAdcf>$|?SkF% zh+N0xyWH;;FnU!zxhtWnRL|AcaXa6|f`PT<{MjFuGoIZ&(3dOuYgbpU-`Oy)@Lw_Z z&%X;Du3Mk<<7Mo!XRFt}`~UaIX(0<4zV^cp3#dr_JR0EBUUqW*^W^ALe*_NgTDLq+ zD#(1^y2}F3LT(+m)Xl7zd93E2&tLa_w~Hf}+seOQo>ya?^H<_heBPZ;`wZXk|2zKS Quj>KcVudYUnh6XH08r|NaR2}S literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/fake-gzip.html.gz b/src/test/resources/htmltests/fake-gzip.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..54b0682058538d3ad22d39c7feeb5721af5a405b GIT binary patch literal 101 zcmV-r0Gj_FiwFoQ5L{jW17=}sWi4lVX>cxRbZu+^vnk0e$w{>f$;d2L0HVD75{2}t z%z}c{6dQer6c?9Gft_Psib8QlerXPnD#^^rQAkQvC`wICNleN~)w9tru;T&%Ye8ws HNdN!<vC=2q literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/google-ipod.html b/src/test/resources/htmltests/google-ipod.html deleted file mode 100644 index ba38bb8165..0000000000 --- a/src/test/resources/htmltests/google-ipod.html +++ /dev/null @@ -1,10 +0,0 @@ -<!doctype html><head><title>ipod - Google Search</title><script>window.google={kEI:"uYlKS4SbBoGg6gPf-5XXCw",kEXPI:"17259,17315,22713,23189",kCSI:{e:"17259,17315,22713,23189",ei:"uYlKS4SbBoGg6gPf-5XXCw"},kHL:"en",time:function(){return(new Date).getTime()},log:function(b,d,c){var a=new Image,e=google,g=e.lc,f=e.li;a.onerror=(a.onload=(a.onabort=function(){delete g[f]}));g[f]=a;c=c||"/gen_204?atyp=i&ct="+b+"&cad="+d+"&zx="+google.time();a.src=c;e.li=f+1},lc:[],li:0}; -window.google.sn="web";window.google.timers={load:{t:{start:(new Date).getTime()}}};try{}catch(u){}window.google.jsrt_kill=1; -</script><style>body{background:#fff;color:#000;margin:3px 8px}#gbar{float:left;height:22px}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}#gbs,.gbm{background:#fff;left:0;position:absolute;text-align:left;visibility:hidden;z-index:1000}.gbm{border:1px solid;border-color:#c9d7f1 #36c #36c #a2bae7;z-index:1001}#guser{padding-bottom:7px !important;text-align:right}#gbar,#guser{font-size:13px;padding-top:1px !important}.gb1,.gb3,.gb3i,.gb3f{zoom:1;margin-right:.5em}.gb2,.gb2i,.gb2f{display:block;padding:.2em .5em}a.gb1,a.gb2,a.gb3,a.gb4{color:#00c !important}.gb2,.gb2i,.gb2f,.gb3,.gb3i,.gb3f{text-decoration:none}a.gb2:hover{background:#36c;color:#fff !important}a.gb1,a.gb2,a.gb3,.link{color:#20c!important}.ts{border-collapse:collapse}.ts td{padding:0}.ti,.bl,form,#res h3{display:inline}.ti{display:inline-table}.fl:link,.gl,.gl a:link{color:#77c}a:link,.w,#prs a:visited,#prs a:active,.q:active,.q:visited{color:#20c}.mblink:visited,a:visited{color:#551a8b}a:active{color:red}.cur{color:#a90a08;font-weight:bold}.b{font-weight:bold}.j{width:42em;font-size:82%}.s{max-width:42em}.sl{font-size:82%}#gb{text-align:right;padding:1px 0 7px;margin:0}.hd{position:absolute;width:1px;height:1px;top:-1000em;overflow:hidden}.f,.m,.c h2,#mbEnd h2{color:#676767}.a,cite,.cite,.cite:link{color:green;font-style:normal}#mbEnd{float:right}h1,ol{margin:0;padding:0}li.g,body,html,.std,.c h2,#mbEnd h2,h1{font-size:small;font-family:arial,sans-serif}.c h2,#mbEnd h2,h1{font-weight:normal}#ssb,.clr{clear:both;margin:0 8px}#nav a,#nav a:visited,.blk a{color:#000}#nav a{display:block}#nav .b a,#nav .b a:visited{color:#20c}#nav .i{color:#a90a08;font-weight:bold}.csb,.ss,#logo span,#rptglbl{background:url(/images/nav_logo7.png) no-repeat;overflow:hidden}.csb,.ss{background-position:0 0;height:26px;display:block}.ss{background-position:0 -88px;position:absolute;left:0;top:0}.cps{height:18px;overflow:hidden;width:114px}.mbi{width:13px;height:13px;background-position:-91px -74px;position:relative;top:2px;margin-right:3px}#nav td{padding:0;text-align:center}#logo{display:block;overflow:hidden;position:relative;width:103px;height:37px;margin:11px 0 7px}#logo img{border:none;position:absolute;left:-0px;top:-26px}.pin{overflow:hidden;height:36px;width:22px;position:relative;display:block}.pin img{position:absolute}.pin1{top:0}.pin2{top:-36px}.pin3{top:-72px}.pin4{top:-108px}.pin5{top:-144px}.pin6{top:-180px}.pin7{top:-216px}.pindot{bottom:0px}#logo span,.ch{cursor:pointer}.lst{font-family:arial,sans-serif;font-size:17px;vertical-align:middle}.lsb{-webkit-appearance:button;padding:0 8px;border:1px solid #999;-webkit-border-radius:2px;background:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#ddd));font-family:arial,sans-serif;font-size:15px;height:1.85em;vertical-align:middle}.lsb:active {background:-webkit-gradient(linear,left top,left bottom,from(#ccc),to(#ddd))}h3,.med{font-size:medium;font-weight:normal;padding:0;margin:0}.e{margin:.75em 0}.bc a{color:green;text-decoration:none}.bc a:hover{text-decoration:underline}.slk td{padding-left:40px;padding-top:5px;vertical-align:top}.slk div{padding-left:10px;text-indent:-10px}.fc{margin-top:.5em;padding-left:3em}#mbEnd cite{display:block;text-align:left}#mbEnd p{margin:-.5em 0 0 .5em;text-align:center}#bsf,#ssb,.blk{border-top:1px solid #6b90da;background:#f0f7f9}#bsf{border-bottom:1px solid #6b90da}#flp{margin:7px 0}#ssb div{float:left;padding:4px 0 0;padding-left:7px;padding-right:.5em}#prs a,#prs b{margin-right:.6em}#ssb p{text-align:right;white-space:nowrap;margin:.1em 0;padding:.2em}#ssb{margin:0 8px 11px;padding:.1em}#cnt{max-width:80em;clear:both}#mbEnd{background:#fff;padding:0;border-left:11px solid #fff;border-spacing:0;white-space:nowrap}#res{padding-right:1em;margin:0 16px}.c{background:#fff8dd;margin:0 8px}.c li{padding:0 3px 0 8px;margin:0}.c .tam,.c .tal{padding-top:12px}#mbEnd li{margin:1em 0;padding:0}.xsm{font-size:x-small}.sm{margin:0 0 0 40px;padding:0}ol li{list-style:none}.sm li{margin:0}.gl,#bsf a,.nobr{white-space:nowrap}#mbEnd .med{white-space:normal}.sl,.r{display:inline;font-weight:normal;margin:0}.r{font-size:medium}h4.r{font-size:small}.mr{margin-top:-.5em}.rt1 {background:transparent url(/images/bubble1.png) no-repeat;}.rt2 {background:transparent url(/images/bubble2.png) repeat 0 0 scroll;}.sb {background: url(/images/scrollbar.png) repeat scroll 0 0;cursor:pointer;width:14px;}.rtdm:hover {text-decoration:underline;}.ri_cb{left:0;margin:6px;position:absolute;top:0;z-index:1}.ri_sp{display:-moz-inline-box;display:inline-block;text-align:center;vertical-align:top;margin-bottom:6px}.ri_sp img{vertical-align:bottom}.g{margin:1em 0}.mbl{margin:1em 0 0}em{font-weight:bold;font-style:normal}.tbi div, #tbp{background:url(/images/nav_logo7.png) no-repeat;overflow:hidden;width:13px;height:13px;}#ssb #tbp{background-position:-91px -74px;padding:0;margin-top:1px;margin-left:0.75em;}.tbpo,.tbpc{margin-left:3px;margin-right:1em;text-decoration:underline;white-space:nowrap;}.tbpc,.tbo .tbpo {display:inline}.tbo .tbpc,.tbpo{display:none}#prs *{float:left}#prs a, #prs b{position:relative;bottom:.05em;margin-right:.3em}.std dfn{padding-left:.2em;padding-right:.5em}dfn{font-style:normal;font-weight:bold;padding-left:1px;padding-left:2px;position:relative;top:-.12em}#tbd{display:none;margin-left:-9.6em}.tbo #tads,.tbo #pp,.tbo #tadsb{margin-left:13em}.tbo #res{margin-left:11.05em;}.tbo #tbd{width:9.6em;padding:0;left:11px;background:#fff;border-right:1px solid #c9d7f1;position:absolute;display:block;margin-left:0}.tbo #mbEnd{width:26%}</style><script>google.y={};google.x=function(e,g){google.y[e.id]=[e,g];return false};if(!window.google)window.google={};window.google.crm={};window.google.cri=0;window.clk=function(d,e,f,j,k,l,m){if(document.images){var a=encodeURIComponent||escape,b=new Image,g=window.google.cri++;window.google.crm[g]=b;b.onerror=(b.onload=(b.onabort=function(){delete window.google.crm[g]}));b.src=["/url?sa=T","\x26source\x3dweb",e?"&oi="+a(e):"",f?"&cad="+a(f):"","&ct=",a(j||"res"),"&cd=",a(k),"&ved=",a(m),d?"&url="+a(d.replace(/#.*/,"")).replace(/\+/g,"%2B"):"","&ei=","uYlKS4SbBoGg6gPf-5XXCw",l].join("")} -return true}; -window.gbar={qs:function(){},tg:function(e){var o={id:'gbar'};for(i in e)o[i]=e[i];google.x(o,function(){gbar.tg(o)})}};</script> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body id=gsr topmargin=3 marginheight=3><textarea id=csi style=display:none></textarea><div id=gbar><nobr><b class=gb1>Web</b> <a href="http://images.google.com/images?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wi" onclick=gbar.qs(this) class=gb1>Images</a> <a href="http://video.google.com/videosearch?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wv" onclick=gbar.qs(this) class=gb1>Videos</a> <a href="http://maps.google.com/maps?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wl" onclick=gbar.qs(this) class=gb1>Maps</a> <a href="http://news.google.com/news?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wn" onclick=gbar.qs(this) class=gb1>News</a> <a href="http://www.google.com/products?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wf" onclick=gbar.qs(this) class=gb1>Shopping</a> <a href="http://mail.google.com/mail/?hl=en&tab=wm" class=gb1>Gmail</a> <a href="http://www.google.com/intl/en/options/" onclick="this.blur();gbar.tg(event);return !1" aria-haspopup=true class=gb3><u>more</u> <small>&#9660;</small></a><div class=gbm id=gbi><a href="http://books.google.com/books?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wp" onclick=gbar.qs(this) class=gb2>Books</a> <a href="http://www.google.com/finance?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=we" onclick=gbar.qs(this) class=gb2>Finance</a> <a href="http://translate.google.com/translate_t?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wT" onclick=gbar.qs(this) class=gb2>Translate</a> <a href="http://scholar.google.com/scholar?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=ws" onclick=gbar.qs(this) class=gb2>Scholar</a> <a href="http://blogsearch.google.com/blogsearch?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wb" onclick=gbar.qs(this) class=gb2>Blogs</a> <div class=gb2><div class=gbd></div></div><a href="http://www.youtube.com/results?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=w1" onclick=gbar.qs(this) class=gb2>YouTube</a> <a href="http://www.google.com/calendar/render?hl=en&tab=wc" class=gb2>Calendar</a> <a href="http://picasaweb.google.com/lh/view?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wq" onclick=gbar.qs(this) class=gb2>Photos</a> <a href="http://docs.google.com/?hl=en&tab=wo" class=gb2>Documents</a> <a href="http://www.google.com/reader/view/?hl=en&tab=wy" class=gb2>Reader</a> <a href="http://sites.google.com/?hl=en&tab=w3" class=gb2>Sites</a> <a href="http://groups.google.com/groups?hl=en&q=ipod&um=1&ie=UTF-8&sa=N&tab=wg" onclick=gbar.qs(this) class=gb2>Groups</a> <div class=gb2><div class=gbd></div></div><a href="http://www.google.com/intl/en/options/" class=gb2>even more &raquo;</a> </div></nobr></div><div id=guser width=100%><nobr><a href="/history/optout?hl=en" class=gb4>Web History</a> | <a href="/preferences?hl=en" class=gb4>Search settings</a> | <a href="https://www.google.com/accounts/Login?hl=en&continue=http://www.google.com/search%3Fhl%3Den%26q%3Dipod%26aq%3Df%26aqi%3Dg10" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div><div id=cnt><form id=tsf name=gs method=GET action="/search"><table id=sft class=ts style="clear:both;margin:19px 16px 20px 15px"><tr valign=top><td><h1><a id=logo href="http://www.google.com/webhp?hl=en" title="Go to Google Home">Google<img width=164 height=106 src="/images/nav_logo7.png" alt=""></a></h1><td id=sff style="padding:1px 3px 7px;padding-left:16px;width:100%"><table class=ts style="margin:12px 0 3px"><tr><td nowrap><input type=hidden name=hl value="en"><input autocomplete="off" class=lst type=text name=q size=41 maxlength=2048 value="ipod" title="Search"> <input type=submit name="btnG" class=lsb style="margin:0 2px 0 5px" value="Search"></td><td style="padding:0 6px" class="nobr xsm"><a href="/advanced_search?q=ipod&amp;hl=en">Advanced Search</a><br></table></table></form><div id=ssb><div id=prs><span class=std><b>Web</b></span><a href="/search?hl=en&amp;q=ipod&amp;tbo=1" id=tbpi class=q onclick="return google.x(this,function(){return google.Toolbelt.toggle(this, event)})"><div id=tbp></div><span class=tbpo>Hide options</span><span class=tbpc>Show options...</span></a></div><p id=resultStats>&nbsp;Results <b>1</b> - <b>10</b> of about <b>281,000,000</b> for <b>ipod</b> [<a href="/url?q=%2Fdictionary%3Faq%3Df%26langpair%3Den%7Cen%26hl%3Den%26q%3Dipod&amp;r=67&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=dict&amp;ct=d&amp;ved=0CAQQvQE&amp;usg=AFQjCNHlBwFMws5lbd1NWDX_Sc89YCgnKg" title="Look up definition of ipod">definition</a>]<b></b>. (<b>0.20</b> seconds)&nbsp;</div><div id=tbd class=med><h2 class=hd></h2></div><table id=mbEnd width=30% style=margin-bottom:1em><tr><td id=rhsline style="border-left:1px solid #c9d7f1;padding-left:8px" class=std><h2 style="margin:0;padding:2px 0 0;text-align:left">Sponsored Links</h2><ol onmouseover="return true" class=nobr><li><h3><a id=an1 href="/aclk?sa=l&amp;ai=CHxzLuYlKS8LTBo-47AOnupCJAv3QlqUBua2t2w79he0FEAEoBVDQlY61-f____8BYKW4moCcAaABs_La7APIAQGpAjCGJIGuB7g-qgQcT9CKNXzBrEjGFup1RdBRt5ikEaNGbEPLPLoe_w&amp;num=3&amp;sig=AGiWqtxpEN6jIIS76dPhqtSgq7O3FYBKCg&amp;q=http://www.bidrivals.com/au/" >Buy <b>Ipod</b></a></h3><b>Ipod</b> at the market&#39;s<br>lowest price. Deal of a lifetime!<br><cite>www.bidrivals.com/au</cite><li><h3><a id=an2 href="/aclk?sa=l&amp;ai=CH0hUuYlKS8LTBo-47AOnupCJApuP8q4Bm8DPihD8vPwFEAIoBVC5k_taYKW4moCcAaABva3--wPIAQGpAlGPSVP4xK4-qgQcT9CqCGDBq0jGFup1RdBtucmjEaNGbEPLPLoe_w&amp;num=4&amp;sig=AGiWqtyhZeaukMr6dY--sm4w5YYq1cwyuQ&amp;q=http://www.cataloguecentral.com.au/Pages/ViewCatalogue.aspx%3Fr%3DKmart%26src%3D7%26rid%3D32%26id%3D7067%26sp%3D1" ><b>Ipod</b> Catalogue</a></h3>One Stop School Shop Catalogue.<br>You Won&#39;t Find It Cheaper Anywhere!<br><cite>www.Kmart.CatalogueCentral.com.au</cite><li><h3><a id=an3 href="/aclk?sa=l&amp;ai=Cjaf5uYlKS8LTBo-47AOnupCJAqnjq60B-4rHlQz8vPwFEAMoBVDCxfLy-_____8BYKW4moCcAaABz4f_7QPIAQGpAlGPSVP4xK4-qgQcT9CqJnrBqkjGFup1RdBnmvWjEaNGbEPLPLoe_w&amp;num=5&amp;sig=AGiWqtz-5yEYM9klXBYIhBovy_7VyAP3uA&amp;q=http://pixel2151.everesttech.net/2151/r/3/s_b0d03b0eea3367c97d912713974d966a_3171205561/url%3Dhttp%253A//www.choice.com.au/shop/choiceLinkup.asp%253Fsku%253DOA106460%2526rid%253DKQBU5A3R8L9K9HE78G99C0AHEMAHDJQB" ><b>Ipod</b></a></h3>We&#39;ve Reviewed 12 MP3 Players<br>Make The Right Decision With CHOICE<br><cite>www.CHOICE.com.au/Mp3Players</cite><li><h3><a id=an4 href="/aclk?sa=l&amp;ai=CfnZQuYlKS8LTBo-47AOnupCJAt3A46UBic-qmxL8vPwFEAQoBVDl6afH_P____8BYKW4moCcAaAB06nc_gPIAQGpAt2byJRbz7o-qgQZT9D6VGzBqUjGXurzaXjDmAN9Jw0pYs90yA&amp;num=6&amp;sig=AGiWqtwiKK6Gz3XRr44PavyC8gxMZnzFpQ&amp;q=http://aus.Apple-Deals.info/ipod.php%3Fkw%3Dipod" >Cheap Used <b>iPods</b> For Sale</a></h3>New &amp; Used <b>iPods</b> For Sale<br>in Australia. Grab a Bargain today!<br><cite>Apple-Deals.info/<b>iPod</b></cite><li><h3><a id=an5 href="/aclk?sa=L&amp;ai=CLulVuYlKS8LTBo-47AOnupCJAueI5ZUBm7ONsQ79he0FEAUoBVDm-9HBA2CluJqAnAHIAQGqBBlP0Jo4bsGoSMZe6qdfSMOYA30nDSliz3TI&amp;num=7&amp;sig=AGiWqtyONL3uBSu8NgnxFdfPqnh803vxIA&amp;q=http://altfarm.mediaplex.com/ad/ck/9859-59076-2056-0%3Fkw%3DFor%2520Sale-GEO-Australia-Audio%2520-%2520Personal-ipod-g-sn%26mpro%3Dhttp://sydney.gumtree.com.au/f-Stuff-for-Sale-tv-audio-music-W0QQCatIdZ18316%3Futm_source%3Dgoogle%26utm_medium%3Dcpc%26utm_term%3Dipod%26utm_campaign%3DFor%2520Sale-GEO-Australia" ><b>Ipod</b></a></h3>Find amazing bargains for MP3<br>players, minidiscs &amp; discmans!<br><cite>www.gumtree.com.au</cite></ol><br><p style="margin:-14px 0 0;text-align:left"><a href="https://adwords.google.com/select/Login?sourceid=awo&subid=-en-et-symh&medium=link&hl=en" class=fl>See your ad here&nbsp;&raquo;</a><br><br><tr><td id=rhspad></table><div class=c id=tads><h2 style="float:right;margin:3px 3px 0">Sponsored Links</h2><ol onmouseover="return true" style="padding:3px 0"><li class=taf><h3><a id=pa1 href="/aclk?sa=L&amp;ai=Ch_kSuYlKS8LTBo-47AOnupCJAuy00IABsOG94w78vPwFCAAQASgCUKyqqOf9_____wFgpbiagJwByAEBqQJRj0lT-MSuPqoEGU_Qqhl_wa5Ixl7q9HBIw5gDfScNKWLPdMg&amp;sig=AGiWqtzSHCuUg5BLwGf2xmgpA0JoRkYDdQ&amp;q=http://akatracking.esearchvision.com/esi/redirect.html%3Fesvt%3D101758-GOAUE%26esvq%3Dipod%26esvadt%3D999999-5070368-1082675-1%26esvcrea%3D3869157590%26esvplace%3D%26transferparams%3D1%26esvaid%3D320%26url%3Dhttp%253a%252f%252fstore.apple.com%252fau%252fgo%252fipod%253faosid%253dp202%2526cid%253dAOS-AP-AU-Google-AA0000018610" ><b>iPod</b> at The Apple® Store</a></h3><cite>store.apple.com/au/<b>ipod</b></cite>&nbsp; &nbsp; &nbsp; New <b>iPod</b> nano, <b>iPod</b> touch, <b>iPod</b> shuffle &amp; <b>iPod</b> classic. Ships free.<table class=slk><tr><td nowrap style="padding-left:40px"><a href="/aclk?sa=L&amp;ai=CNYNYuYlKS8LTBo-47AOnupCJAuy00IABsOG94w78vPwFCAAQASgCUOqu1tX9_____wFgpbiagJwByAEBqQJRj0lT-MSuPqoEGU_Qqhl_wa5Ixl7q9HBIw5gDfScNKWLPdMg&amp;sig=AGiWqtzwyWSN6h8MiI7KPIqknlz2q84I2w&amp;q=http://akatracking.esearchvision.com/esi/redirect.html%3Fesvt%3D101758-GOAUE%26esvq%3Dipod%26esvadt%3D999999-1561893-1084672-1%26esvcrea%3D3869157590%26esvplace%3D%26transferparams%3D1%26esvaid%3D320%26url%3Dhttp%253a%252f%252fstore.apple.com%252fau%252fgo%252fmac%253faosid%253dp202%2526cid%253dAOS-AP-AU-Google-AA0000018642">Shop Mac</a><br><a href="/aclk?sa=L&amp;ai=CpjtCuYlKS8LTBo-47AOnupCJAuy00IABsOG94w78vPwFCAAQASgCUNPip6n______wFgpbiagJwByAEBqQJRj0lT-MSuPqoEGU_Qqhl_wa5Ixl7q9HBIw5gDfScNKWLPdMg&amp;sig=AGiWqtxwX2zW5GowJ6S_zrviVj7lllzzpA&amp;q=http://akatracking.esearchvision.com/esi/redirect.html%3Fesvt%3D101758-GOAUE%26esvq%3Dipod%26esvadt%3D999999-5070368-1093775-1%26esvcrea%3D3869157590%26esvplace%3D%26transferparams%3D1%26esvaid%3D320%26url%3Dhttp%253a%252f%252fstore.apple.com%252fau%252fgo%252fipod%253faosid%253dp202%2526cid%253dAOS-AP-AU-Google-AA0000018642">Shop iPod</a><td nowrap style="padding-left:20px"><a href="/aclk?sa=L&amp;ai=CjFg8uYlKS8LTBo-47AOnupCJAuy00IABsOG94w78vPwFCAAQASgCUKC1nNYHYKW4moCcAcgBAakCUY9JU_jErj6qBBlP0KoZf8GuSMZe6vRwSMOYA30nDSliz3TI&amp;sig=AGiWqtx_TsVR3MMbS4MfnC4yrCWshnvy-g&amp;q=http://akatracking.esearchvision.com/esi/redirect.html%3Fesvt%3D101758-GOAUE%26esvq%3Dipod%26esvadt%3D999999-4980847-1082945-1%26esvcrea%3D3869157590%26esvplace%3D%26transferparams%3D1%26esvaid%3D320%26url%3Dhttp%253a%252f%252fstore.apple.com%252fau%252fgo%252fiphone3g%253faosid%253dp202%2526cid%253dAOS-AP-AU-Google-AA0000018642">Shop iPhone</a><br><a href="/aclk?sa=L&amp;ai=CucEhuYlKS8LTBo-47AOnupCJAuy00IABsOG94w78vPwFCAAQASgCULSzxKUCYKW4moCcAcgBAakCUY9JU_jErj6qBBlP0KoZf8GuSMZe6vRwSMOYA30nDSliz3TI&amp;sig=AGiWqtzj03NcnX1-X8FR0eSKIT2TnOi3SA&amp;q=http://akatracking.esearchvision.com/esi/redirect.html%3Fesvt%3D101560-GOAUE%26esvq%3Dipod%26esvadt%3D999999-5315592-1048419-1%26esvcrea%3D3869157590%26esvplace%3D%26transferparams%3D1%26esvaid%3D320%26url%3Dhttp%253a%252f%252fstore.apple.com%252fau%252fgo%252fpromo%252fbacktoschool%253faosid%253dp202%2526cid%253dAOS-AP-AU-Google-AA0000018632">Back to Uni - Apple Store</a></table><li class=tal><h3><a id=pa2 href="/aclk?sa=L&amp;ai=Cc3EkuYlKS8LTBo-47AOnupCJAun5pKwBmfymzA_3_dgFCAAQAigCUN2z3eAHYKW4moCcAcgBAakCUY9JU_jErj6qBBxP0OpRYsGtSMYW6nVF0E_Ykb8Ro0ZsQ8s8uh7_&amp;sig=AGiWqtyJjwjqy7gfddbDtAnD2egXs-ga0A&amp;q=http://www.harveynorman.com.au/page/1255508460073/portable-electronics-audio-video-ipod-mp3-players" >Buy <b>iPods</b></a></h3><cite>www.harveynorman.com.au</cite>&nbsp; &nbsp; &nbsp; Get great prices on <b>iPods</b> at A Harvey Norman store near you.</ol></div><div id=res class=med><!--a--><h2 class=hd>Search Results</h2><div><ol><li class=g><h3 class=r><a href="http://news.google.com/news?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=news_group&amp;ct=title&amp;resnum=1&amp;ved=0CCIQsQQwAA">News results for <em>ipod</em></a></h3><div class=s><table class=ts><tr><td valign=top style="padding-top:5px;padding-right:10px;font-size:78%;line-height:normal;width:80px;text-align:center"><a href="/url?q=http://www.sfgate.com/cgi-bin/blogs/techchron/detail%3F%26entry_id%3D54548&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=news_group&amp;resnum=1&amp;ct=image&amp;ved=0CCAQpwIwAA&amp;usg=AFQjCNGBP-I6Eg1XNsesqAvZBSXJrfuU0w" style="text-decoration:none" ><img src="http://news.google.com/news/tbn/QKV6MOaNPkIJ" alt="" border=1 width=80 height=37><br><span style="text-decoration:underline"></span></a><td valign=top style="padding-top:3px"><!--m--><a href="http://www.sfgate.com/cgi-bin/blogs/techchron/detail?&amp;entry_id=54548" class=l onmousedown="return clk(this.href,'news_result','','res','1','','0CBcQqQIwAA')">CES2010: L5 accessory turns iPhone or <em>iPod</em> Touch into universal remote</a>‎ - <nobr><span class=f>6 days ago</span></nobr><br><div>At the Consumer Electronics Show this week, L5 Technology will be showing off a new $49.95 L5 Remote accessory for the iPhone and <em>iPod</em> Touch that allows <b>...</b></div><span class=gl><cite>San Francisco Chronicle (blog)</cite> - <a href="http://news.google.com/news/story?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;ncl=dv-OIjTUKHF0eOMcWf567yv6LXclM&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=news_result&amp;ct=more-results&amp;resnum=1&amp;ved=0CBgQqgIwAA">588 related articles&nbsp;&raquo;</a></span><br><!--n--><!--m--><div><a href="http://cgi.money.cnn.com/tools/redirect.jsp?url=http://brainstormtech.blogs.fortune.cnn.com/2010/01/08/how-many-ipods-did-apple-sell/" class=l onmousedown="return clk(this.href,'news_result','','res','2','','0CBoQqQIwAQ')">How many <em>iPods</em> did Apple sell?</a>‎ - <span class=gl><cite>CNNMoney.com (blog)</cite> - <a href="http://news.google.com/news/story?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;ncl=dydwbYh1CAUHOvM&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=news_result&amp;ct=more-results&amp;resnum=2&amp;ved=0CBsQqgIwAQ" >60 related articles&nbsp;&raquo;</a></span></div><!--n--><!--m--><div><a href="http://www.computerworld.com/s/article/9143083/Nokia_asks_court_to_bar_U.S._imports_of_Apple_s_Macs_iPhones_iPods?taxonomyId=1" class=l onmousedown="return clk(this.href,'news_result','','res','3','','0CB0QqQIwAg')">Nokia asks court to bar US imports of Apple&#39;s Macs, iPhones, <em>iPods</em></a>‎ - <span class=gl><cite>Computerworld</cite> - <a href="http://news.google.com/news/story?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;ncl=dhkvlWd0MdqvItM&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=news_result&amp;ct=more-results&amp;resnum=3&amp;ved=0CB4QqgIwAg" >243 related articles&nbsp;&raquo;</a></span></div><!--n--></table></div><!--m--><li class=g><h3 class=r><a href="http://www.apple.com/itunes/" class=l onmousedown="return clk(this.href,'','','res','4','','0CCMQFjAD')">Apple - Download music and more with iTunes. Play it all on <em>iPod</em>.</a></h3><div class="s">Learn about <em>iPod</em>, Apple TV, and accessories. Download iTunes software free and purchase iTunes Gift Cards. Check out the most popular TV shows, movies, <b>...</b><br><cite>www.apple.com/itunes/ - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:cM0PdWG3cZsJ:www.apple.com/itunes/+ipod&amp;cd=4&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','4','')">Cached</a> - <a href="/search?hl=en&amp;q=related:www.apple.com/itunes/+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CCQQHzAD">Similar</a></span><br><table class=slk><tr><td><div><a href="/url?q=http://apple.com/itunes/download&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=1&amp;ved=0CCUQqwMoAA&amp;usg=AFQjCNE9cqEqfSRN5wg5tFhWDoplZ9DLmA">Download iTunes Now</a></div><div><a href="/url?q=http://www.apple.com/ipodtouch/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=2&amp;ved=0CCYQqwMoAQ&amp;usg=AFQjCNHTzrt6TSKlk3_y4LWUO85iUKQcDw">iPod Touch</a></div><div><a href="/url?q=http://www.apple.com/itunes/overview/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=3&amp;ved=0CCcQqwMoAg&amp;usg=AFQjCNGLYbqm9J282VMvVj1bvBgIpUd-Sg">iTunes Overview</a></div><div><a href="/url?q=http://www.apple.com/itunes/how-to/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=4&amp;ved=0CCgQqwMoAw&amp;usg=AFQjCNGQMReozCIBoTTDJvhjXwXQOiLLEw">Tutorials</a></div></td><td style="padding-left:20px"><div><a href="/url?q=http://www.apple.com/itunes/whats-on/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=5&amp;ved=0CCkQqwMoBA&amp;usg=AFQjCNFalLgviqs3qgVUc1Mq0AcrRe9aQw">What&#39;s on iTunes</a></div><div><a href="/url?q=http://www.apple.com/ipodclassic/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=6&amp;ved=0CCoQqwMoBQ&amp;usg=AFQjCNHCRx3y-kLzPp6TEDgJNuofEL5kdw">iPod classic</a></div><div><a href="/url?q=http://www.apple.com/ipodshuffle/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=7&amp;ved=0CCsQqwMoBg&amp;usg=AFQjCNH_BPBuhDRyYzRTBJ1arDH5yCewBg">iPod shuffle</a></div><div><a href="/url?q=http://www.apple.com/itunes/digital-music-basics/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=result&amp;cd=8&amp;ved=0CCwQqwMoBw&amp;usg=AFQjCNElWgQPJhmziUFAfbfEfyAi9VKtzg">Digital Music Basics</a></div></td></tr><tr><td colspan=2><a class=fl href="/search?hl=en&amp;q=+site:apple.com+ipod&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=smap&amp;resnum=4&amp;ct=more-results&amp;ved=0CC0QrAM">More results from apple.com&nbsp;&raquo;</a></td></tr></table></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://www.apple.com/ipodclassic/" class=l onmousedown="return clk(this.href,'','','res','5','','0CDAQFjAE')">Apple - <em>iPod</em> classic - Hold 40000 songs in your pocket.</a></h3><div class="s">With 160GB of storage, <em>iPod</em> classic is the take-everything-everywhere <em>iPod</em>, with space for up to 40000 songs, 200 hours of video, or 25000 photos.<br><cite>www.apple.com/<b>ipod</b>classic/ - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:2y64WAehIdMJ:www.apple.com/ipodclassic/+ipod&amp;cd=5&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','5','')">Cached</a> - <a href="/search?hl=en&amp;q=related:www.apple.com/ipodclassic/+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CDEQHzAE">Similar</a></span></div><!--n--><li class=g><h3 class=r><a href="http://www.google.com/products?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=product_result_group&amp;ct=title&amp;resnum=6&amp;ved=0CEEQrQQwBQ">Shopping results for <em>ipod</em></a></h3><table class=ts><tr><td valign=top width=80 style="padding-right:8px;padding-top:6px"><a href="http://www.google.com/products?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=product_result_group&amp;ct=image&amp;resnum=6&amp;ved=0CD8QzAMwBQ"><img src="http://base0.googlehosted.com/base_media?q=FroogleCatalog_CNETI835912.jpg&amp;size=18&amp;dhm=4cf8a506&amp;hl=en" width=80 height=80 alt="" border=1></a><td valign=top style="padding-top:4px"><!--m--><div style="padding-bottom:2px"><a href="http://www.google.com/products/catalog?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;cid=13561372809271777520&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=product_catalog_result&amp;ct=result&amp;resnum=6&amp;ved=0CDMQ8wIwBQ#ps-sellers">Apple <em>iPod</em> Touch 32 GB (3rd Generation)</a><br><table border=0 cellpadding=0 cellspacing=0 class="ti" style="height:px;width:px"><tr><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="Rated 4.4 out of 5.0" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-10px;position:absolute;top:-78px"></table> 528 reviews -&nbsp;$270 new, $260 used - 37 stores</div><!--n--><!--m--><div style="padding-bottom:2px"><a href="http://www.google.com/products/catalog?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;cid=6823974292712536453&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=product_catalog_result&amp;ct=result&amp;resnum=7&amp;ved=0CDcQ8wIwBg#ps-sellers">Apple <em>iPod</em> Touch 8 GB (3rd Generation)</a><br><table border=0 cellpadding=0 cellspacing=0 class="ti" style="height:px;width:px"><tr><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="Rated 4.4 out of 5.0" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-10px;position:absolute;top:-78px"></table> 528 reviews -&nbsp;$173 new, $162 used - 29 stores</div><!--n--><!--m--><div style="padding-bottom:2px"><a href="http://www.google.com/products/catalog?hl=en&amp;q=ipod&amp;um=1&amp;ie=UTF-8&amp;cid=1464570492142507412&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=product_catalog_result&amp;ct=result&amp;resnum=8&amp;ved=0CDsQ8wIwBw#ps-sellers">Apple <em>iPod</em> Touch 64 GB (3rd Generation)</a><br><table border=0 cellpadding=0 cellspacing=0 class="ti" style="height:px;width:px"><tr><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="Rated 4.4 out of 5.0" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-20px;position:absolute;top:-78px"><td><p style="height:9px;width:10px;margin:0;padding:0;position:relative;overflow:hidden"><img alt="" src="/images/nav_logo7.png" style="left:-10px;position:absolute;top:-78px"></table> 528 reviews -&nbsp;$349 new, $325 used - 44 stores</div><!--n--></table><!--m--><li class=g><h3 class=r><a href="http://en.wikipedia.org/wiki/IPod" class=l onmousedown="return clk(this.href,'','','res','9','','0CEIQFjAI')"><em>iPod</em> - Wikipedia, the free encyclopedia</a></h3><div class="s">The <em>iPod</em> is a portable media player designed and marketed by Apple and launched on October 23, 2001. The product line-up includes the hard drive-based <em>iPod</em> <b>...</b><br><cite>en.wikipedia.org/wiki/<b>IPod</b> - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:gsN3B5CBDpcJ:en.wikipedia.org/wiki/IPod+ipod&amp;cd=9&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','9','')">Cached</a> - <a href="/search?hl=en&amp;q=related:en.wikipedia.org/wiki/IPod+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CEMQHzAI">Similar</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://www.cnet.com/ipod/" class=l onmousedown="return clk(this.href,'','','res','10','','0CEUQFjAJ')">Apple <em>iPod</em> reviews, news and videos - CNET.com</a></h3><div class="s">Check out CNET&#39;s <em>iPod</em> coverage for the latest Apple <em>iPod</em> reviews, news and videos of the <em>iPod</em> Nano, <em>iPod</em> Shuffle, <em>iPod</em> Touch, <em>iPod</em> Classic and <em>iPod</em> <b>...</b><br><cite>www.cnet.com/<b>ipod</b>/ - <span>22 minutes ago - </span></cite><span class=gl><a href="http://74.125.153.132/search?q=cache:4kGjltMQJwsJ:www.cnet.com/ipod/+ipod&amp;cd=10&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','10','')">Cached</a> - <a href="/search?hl=en&amp;q=related:www.cnet.com/ipod/+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CEYQHzAJ">Similar</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://www.ilounge.com/" class=l onmousedown="return clk(this.href,'','','res','11','','0CEgQFjAK')">All things <em>iPod</em>, iPhone, iTunes and beyond | iLounge</a></h3><div class="s">Includes news, information, reviews, a discussion forum, and tips and tricks for all <em>iPods</em> and iPhones.<br><cite>www.ilounge.com/ - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:CqbO6dHC2eoJ:www.ilounge.com/+ipod&amp;cd=11&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','11','')">Cached</a> - <a href="/search?hl=en&amp;q=related:www.ilounge.com/+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CEkQHzAK">Similar</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://www.amazon.com/iPod-Computers/b?ie=UTF8&node=13660271" class=l onmousedown="return clk(this.href,'','','res','12','','0CEsQFjAL')">Amazon.com: <em>iPod</em>: Electronics</a></h3><div class="s">On September 9, Apple launched a host of new <em>iPods</em>, including 18 nanos that now feature a built-in video camera, FM radio, and pedometer; six shuffles in <b>...</b><br><cite>www.amazon.com/<b>iPod</b>-Computers/b?ie=UTF8&amp;node... - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:B-GKtdWb_ggJ:www.amazon.com/iPod-Computers/b%3Fie%3DUTF8%26node%3D13660271+ipod&amp;cd=12&amp;hl=en&amp;ct=clnk" onmousedown="return clk('http://74.125.153.132/search?q=cache:B-GKtdWb_ggJ:www.amazon.com/iPod-Computers/b%3Fie%3DUTF8%26node%3D13660271+ipod&cd=12&hl=en&ct=clnk','','','clnk','12','')">Cached</a> - <a href="/search?hl=en&amp;q=related:www.amazon.com/iPod-Computers/b%3Fie%3DUTF8%26node%3D13660271+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CEwQHzAL">Similar</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://store.apple.com/" class=l onmousedown="return clk(this.href,'','','res','13','','0CE4QFjAM')">Welcome to the Apple Store - Apple Store (U.S.)</a></h3><div class="s">Shop for Apple computers, compare <em>iPod</em> and iPhone models, and discover Apple and third-party accessories, software, and much more.<br><cite>store.apple.com/ - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:GyR1xOuVipoJ:store.apple.com/+ipod&amp;cd=13&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','13','')">Cached</a> - <a href="/search?hl=en&amp;q=related:store.apple.com/+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CE8QHzAM">Similar</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://www.savebuckets.co.uk/browse/consumer-electronics/audio-hi-fi/portable-devices/multi-media-players/ipod/" class=l onmousedown="return clk(this.href,'','','res','14','','0CFEQFjAN')">Cheap <em>iPods</em> - Best Prices <em>iPod</em> &amp; <em>iPod</em> Touch | Save Money</a></h3><div class="s">Savebuckets has an enormous selection of the latest <em>iPods</em>. Compare prices of new generation <em>iPods</em>: Classic, Nano, Shuffle &amp; Touch.<br><cite><span class=bc>www.savebuckets.co.uk &rsaquo; ... &rsaquo; <a href="/url?q=http://www.savebuckets.co.uk/browse/consumer-electronics/audio-hi-fi/portable-devices/multi-media-players/&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=breadcrumbs&amp;resnum=14&amp;ct=result&amp;cd=1&amp;ved=0CFIQ6QUoAQ&amp;usg=AFQjCNEEsiPMj-pSGDSaqk6CtukkEHRS8A">Multi Media Players</a></span> - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:T2Q5eM6pBUEJ:www.savebuckets.co.uk/browse/consumer-electronics/audio-hi-fi/portable-devices/multi-media-players/ipod/+ipod&amp;cd=14&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','14','')">Cached</a> - <a href="/search?hl=en&amp;q=related:www.savebuckets.co.uk/browse/consumer-electronics/audio-hi-fi/portable-devices/multi-media-players/ipod/+ipod&amp;sa=X&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;ved=0CFQQHzAN">Similar</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://www.blendtec.com/willitblend/videos.aspx?type=unsafe&video=ipod" class=l onmousedown="return clk(this.href,'','','res','15','','0CFYQFjAO')"><em>iPod</em> - Blendtec - Home of mixers, blenders, grain mills and more</a></h3><div class="s">At WillItBlend.com we take everything but the kitchen sink, stick it in a blender, and ask, will it blend?<br><cite>www.blendtec.com/willitblend/videos.aspx?type=unsafe...<b>ipod</b> - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:lm5bd9lEZlcJ:www.blendtec.com/willitblend/videos.aspx%3Ftype%3Dunsafe%26video%3Dipod+ipod&amp;cd=15&amp;hl=en&amp;ct=clnk" onmousedown="return clk('http://74.125.153.132/search?q=cache:lm5bd9lEZlcJ:www.blendtec.com/willitblend/videos.aspx%3Ftype%3Dunsafe%26video%3Dipod+ipod&cd=15&hl=en&ct=clnk','','','clnk','15','')">Cached</a></span></div><!--n--><!--m--><li class=g><h3 class=r><a href="http://ipodlinux.org/" class=l onmousedown="return clk(this.href,'','','res','16','','0CFgQFjAP')">iPodLinux :: Home</a></h3><div class="s">6 Mar 2009 <b>...</b> October 10, 2009. 2:20 pm, The broken dates are now fixed on our website. Forums and Wiki are working as expected again! <b>...</b><br><cite><b>ipod</b>linux.org/ - </cite><span class=gl><a href="http://74.125.153.132/search?q=cache:VuikJc1beWcJ:ipodlinux.org/+ipod&amp;cd=16&amp;hl=en&amp;ct=clnk" onmousedown="return clk(this.href,'','','clnk','16','')">Cached</a></span></div><!--n--></ol></div><!--z--><div class=e><table class="ts std" id=brs style="padding:0 0 1em"><caption class="med nobr"style="padding-bottom:6px;text-align:left">Searches related to <em>ipod</em></caption><tr><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=ipod+nano&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=1&amp;ved=0CFsQ1QIoAA">ipod <b>nano</b></a><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=ipod+touch&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=2&amp;ved=0CFwQ1QIoAQ">ipod <b>touch</b></a><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=ipod+best+buy&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=3&amp;ved=0CF0Q1QIoAg">ipod <b>best buy</b></a><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=ipod+shuffle&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=4&amp;ved=0CF4Q1QIoAw">ipod <b>shuffle</b></a><tr><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=ipod+support&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=5&amp;ved=0CF8Q1QIoBA">ipod <b>support</b></a><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=ipod+classic&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=6&amp;ved=0CGAQ1QIoBQ">ipod <b>classic</b></a><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=itunes&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=7&amp;ved=0CGEQ1QIoBg"><b>itunes</b></a><td style="padding:0 0 7px;padding-right:34px;vertical-align:top"><a href="/search?hl=en&amp;q=apple&amp;revid=970410033&amp;ei=uYlKS4SbBoGg6gPf-5XXCw&amp;sa=X&amp;oi=revisions_inline&amp;resnum=0&amp;ct=broad-revision&amp;cd=8&amp;ved=0CGIQ1QIoBw"><b>apple</b></a></table></div></div><br clear="all"/><div id=navcnt><table id=nav align=center style="border-collapse:collapse;margin:auto;text-align:center;direction:ltr;margin-bottom:1.4em"><tr valign=top><td class=b><span class="csb" style="background-position:-26px 0;width:18px"></span><td class=cur><span class="csb" style="background-position:-44px 0;width:16px"></span>1<td><a href="/search?hl=en&amp;q=ipod&amp;start=10&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>2</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=20&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>3</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=30&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>4</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=40&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>5</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=50&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>6</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=60&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>7</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=70&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>8</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=80&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>9</a><td><a href="/search?hl=en&amp;q=ipod&amp;start=90&amp;sa=N"><span class="csb ch" style="background-position:-60px 0;width:16px"></span>10</a><td class=b><a href="/search?hl=en&amp;q=ipod&amp;start=10&amp;sa=N"><span class="csb ch" style="background-position:-76px 0;margin-right:34px;width:66px"></span>Next</a></table></div><div style="height:1px;line-height:0"></div><div style="text-align:center;margin-top:1.4em" class=clr><div id=bsf style="padding:1.8em 0;margin-top:0"><form method=get action="/search"><div><input class=lst type=text name=q size=41 maxlength=2048 value="ipod" title="Search"> <input type=submit name="btnG" class=lsb style="margin:0 2px 0 5px" value="Search"><input type=hidden name=hl value="en"><input type=hidden name=sa value="2"></div></form><p style="margin:1.2em 0 0"><a href="/swr?q=ipod&amp;hl=en&amp;swrnum=281000000">Search&nbsp;within&nbsp;results</a> - <a href="/language_tools?q=ipod&amp;hl=en">Language Tools</a> - <a href="/support/websearch/bin/answer.py?answer=134479&amp;hl=en">Search Help</a> - <a href="/quality_form?q=ipod&amp;hl=en" target=_blank>Dissatisfied? Help us improve</a> - <a href="/experimental/">Try Google Experimental</a></div><p id=flp><a href="/">Google&nbsp;Home</a> - <a href="/intl/en/ads/">Advertising&nbsp;Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/privacy.html">Privacy</a> - <a href="/intl/en/about.html">About Google</a></p></div><textarea style="display:none" id=hcache></textarea><div id=xjsd></div><div id=xjsi><script>if(google.y)google.y.first=[];if(google.y)google.y.first=[];google.dstr=[];google.rein=[];window.setTimeout(function(){var a=document.createElement("script");a.src="/extern_js/f/CgJlbiswCjhMQAgsKzAOOAssKzAWOBcsKzAXOAUsKzAYOAQsKzAZOA0sKzAlOMmIASwrMCY4CSwrMCc4AiwrMDw4AiwrMEA4BSwrMEQ4ACwrMEU4ASw/b4CuTnunU9w.js";(document.getElementById("xjsd")||document.body).appendChild(a);if(google.timers&&google.timers.load.t)google.timers.load.t.xjsls=(new Date).getTime();},0); -;window.mbtb1={tbs:"",docid:"15214478937701464389",usg:"b6a0",obd:false};google.base_href='/search?hl\x3den\x26q\x3dipod';google.y.first.push(function(){google.ac.m=1;google.ac.b=true;google.ac.i(document.gs,document.gs.q,'','ipod');google.riu={render:function(){window.setTimeout(function(){var a=document.createElement("script");a.src="/extern_js/f/CgJlbiswPzgCLA/KB1t0AeW2FM.js";(document.getElementById("xjsd")||document.body).appendChild(a);},0); -}};});if(google.j&&google.j.en&&google.j.xi){window.setTimeout(google.j.xi,0);google.fade=null;}</script></div><script>(function(){ -function a(){google.timers.load.t.ol=(new Date).getTime();google.report&&google.timers.load.t.xjs&&google.report(google.timers.load,google.kCSI)}if(window.addEventListener)window.addEventListener("load",a,false);else if(window.attachEvent)window.attachEvent("onload",a);google.timers.load.t.prt=(new Date).getTime(); -})(); -</script></div> \ No newline at end of file diff --git a/src/test/resources/htmltests/google-ipod.html.gz b/src/test/resources/htmltests/google-ipod.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0e2462f50c05e40853f0ceecf660ceb3592b9f9 GIT binary patch literal 12212 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 CaN>#p literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/gzip.html b/src/test/resources/htmltests/gzip.html new file mode 100644 index 0000000000..e063122efb --- /dev/null +++ b/src/test/resources/htmltests/gzip.html @@ -0,0 +1,4 @@ +<title>Gzip test</title> + +<p>This is a gzipped HTML file.</p> + diff --git a/src/test/resources/htmltests/gzip.html.gz b/src/test/resources/htmltests/gzip.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..103f16731f6f6e002a4539eeb9a5746c75d3cf98 GIT binary patch literal 85 zcmV-b0IL5ViwFpt3tV0R17~_^a4u+cZEOIuDakC!NwssY$}CVQNi8n1(T50daoH5u rg=AzFD*#cVLOM{sAT>q7BgEH7AuTf}RnJDh0H_E6+1M5JKL7v#Fclvp literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/gzip.html.z b/src/test/resources/htmltests/gzip.html.z new file mode 100644 index 0000000000000000000000000000000000000000..2e81d53da96ecf2f23bc80c549e14db940a6ab56 GIT binary patch literal 85 zcmV-b0IL5ViwFpt3tV0R3uk(1a4u+cZEOIuDakC!NwssY$}CVQNi8n1(T50daoH5u rg=AzFD*#cVLOM{sAT>q7BgEH7AuTf}RnJDh0H_E6+1M5JKL7v#GIbv; literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/news-com-au-home.html b/src/test/resources/htmltests/news-com-au-home.html deleted file mode 100644 index 191a7cb198..0000000000 --- a/src/test/resources/htmltests/news-com-au-home.html +++ /dev/null @@ -1,3039 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-au" lang="en-au"> -<head> - <!-- Page: News.com.au Top Stories (1225688434416) --> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <title>News.com.au | News from Australia and around the world online | NewsComAu</title> - <meta name="description" content="News from Australia and the world, featuring the latest national, world, business, sport, entertainment and technology news. Drawing on News Limited&#039;s worldwide resources and newspapers. " /> - <meta name="keywords" content="News, Breaking News, Latest News, Australia News, World News, Video" /> - <meta http-equiv="refresh" content="240" /> - <script type="text/javascript"> - //<![CDATA[ - var overrideNdmPageSite, overrideNdmPageUs, overridePageType, overrideNdmPageAdsenseGoogleAdClient, overrideNdmPageAdsenseGoogleAdChannel; - //]]> - </script> - <!-- Fragment for [] on [fwprodcontent03.ni.news.com.au] @ [January 11, 2010 12:48PM] [cb:null] --> - <!-- Site includes [NewsComAu:null] last generated at Mon Jan 11 12:48:13 EST 2010 --> - <link rel="stylesheet" media="screen" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/skin.css" /> - <link rel="stylesheet" media="print" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/print.css" /> - <!--[if IE 6]><link rel="stylesheet" media="screen" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/ie6.css" /><![endif]--> - <!--[if IE 7]><link rel="stylesheet" media="screen" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/ie7.css" /><![endif]--> - <link rel="stylesheet" media="screen" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/promotions/2009/11-nov/noughties/noughties.css" /> - <link rel="stylesheet" media="screen" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/promotions/2009/11-nov/lindt/lindt.css" /> - <link rel="stylesheet" media="screen" type="text/css" href="http://resources2.news.com.au/cs/newscomau/css/promotions/2009/12-dec/christmas/christmas.css" /> - <script type="text/javascript" src="http://resources1.news.com.au/cs/js/tanto-min.js"></script> - <script type="text/javascript" src="http://resources.news.com.au/cs/js/network-3rdpartylibs-min.js"></script> - <script type="text/javascript" src="http://resources1.news.com.au/cs/js/base-modules-min.js"></script> - <script type="text/javascript" src="http://resources1.news.com.au/cs/js/site-newscomau-min.js"></script> - <script type="text/javascript" src="http://media.news.com.au/tracking/ndm.track.js"></script> - <link rel="stylesheet" media="print" type="text/css" href="http://resources2.news.com.au/cs/travel/css/print.css" /> - <script type="text/javascript" src="http://sops.news.com.au/adkit/js/kit.js"></script> - <script type="text/javascript"> - //<![CDATA[ - ndm.page.section = "HOME"; - //]]> - </script> - - <!-- Page includes [1225688434416:null] last generated at Mon Jan 11 12:48:13 EST 2010 --> - <script type="text/javascript"> - //<![CDATA[ - ndm.page.vignstoryid = ""; - ndm.page.site = overrideNdmPageSite || "NEWS"; - ndm.page.us = overrideNdmPageUs || "ndmnews"; - ndm.page.runads = true; - ndm.page.adstyles = "auto"; - ndm.page.type = overridePageType || "homepage"; - ndm.page.hbx.account = ""; - ndm.page.hbx.gateway = ""; - ndm.page.hbx.domain = ""; - ndm.page.hbx.pn = ""; - ndm.page.hbx.mlc = ""; - ndm.page.adsense.google_ad_client = overrideNdmPageAdsenseGoogleAdClient || "ca-nd-news_js"; - ndm.page.adsense.google_ad_channel = overrideNdmPageAdsenseGoogleAdChannel || "homepage"; - ndm.page.adsense.google_ad_section = ""; - ndm.page.nielsen = "manual"; - //ndm.page.nielsen.ci = "newscorp"; - //ndm.page.nielsen.cg = ""; - ndm.page.setup(); - //]]> - </script> - <meta http-equiv="X-UA-Compatible" content="IE=8" /> - <meta name="robots" content="noarchive" /> - <link rel="shortcut icon" href="http://resources.news.com.au/cs/newscomau/images/favicon.ico" type="image/x-icon" /> - <link rel="icon" href="http://resources.news.com.au/cs/newscomau/images/favicon.ico" type="image/x-icon" /> - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> -<body class="newscomau homepage no-js"> -<div id="page"> -<div class="ad ad-header"> - <div class="ndmadkit ndmadkit-header"><script type="text/javascript">ndm.kit.header();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-header"><script type="text/javascript">ndm.kit.header();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-header"><script type="text/javascript">ndm.kit.header();</script></div><!-- // .ndmadkit --> -</div> -<div id="skip-links"> - <dl class="skip"> - <dt>Skip to:</dt> - <dd class="first"><a href="#content">Main Content</a></dd> - <dd><a href="#nav">Site Navigation</a></dd> - <dd><a href="#footer">Site Footer</a></dd> - <dd><a href="#site-search">Site Search</a></dd> - <dd><a href="/help/sitemap/">Site Map</a></dd> - <dd><a href="#network-bar">Network Navigation (other sites)</a></dd> - </dl><!-- .skip --> -</div><!-- // #skip-links --> -<div id="network-bar"> - <dl class="network-bar-links"> - <dt class=" first last">Network Links</dt> - <dd class="first "><a href="http://www.news.com.au" rel="track-nin-news">news.com.au</a></dd> - <dd ><a href="http://www.foxsports.com.au" rel="track-nin-news">FOXSPORTS</a></dd> - <dd ><a href="http://www.news.com.au/network/" rel="track-nin-news">Newspapers</a></dd> - <dd ><a href="http://www.careerone.com.au/" rel="track-nin-news">CareerOne</a></dd> - <dd ><a href="http://www.carsguide.com.au/" rel="track-nin-news">carsguide</a></dd> - <dd ><a href="http://www.truelocal.com.au" rel="track-nin-news">TrueLocal</a></dd> - <dd ><a href="http://www.realestate.com.au" rel="track-nin-news">RealEstate</a></dd> - <dd class=" last "><a href="http://au.myspace.com" rel="track-nin-news">MySpace AU</a></dd> -</dl><!-- // .network-bar-links --> - -</div><!-- // #network-bar --> -<div id="header" > - - <div id="header-logo"> - <h1>News.com.au</h1> - </div><!-- // #header-logo --> - <div id="header-ads"> - <div class="ad ad-leaderboard"> - <div class="ndmadkit ndmadkit-leaderboard"><script type="text/javascript">ndm.kit.leaderboard();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-leaderboard"><script type="text/javascript">ndm.kit.leaderboard();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-leaderboard"><script type="text/javascript">ndm.kit.leaderboard();</script></div><!-- // .ndmadkit --> - </div><!-- // .ad .ad-leaderboard --> - </div><!-- // #header-ads --> -</div><!-- // #header --> - <div id="nav"> - <ul id="nav-wrap"> - <li id="nav-level1"> - <ul class="nav-list tier-1"> - <li class="nav-news active first "> - <a href="/">News</a> - </li> - <li class="nav-business "> - <a href="/business">Business</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="/business/breaking-news">Breaking News</a> - </li> - <li > - <a href="/business/markets">Markets</a> - </li> - <li > - <a href="/money/money-matters">Money &amp; Me</a> - </li> - <li > - <a href="/business/business-smarts">Business Smarts</a> - </li> - <li class=" last"> - <a href="/business/archive">Archive</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-money "> - <a href="/money">Money</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="/money/banking">Banking</a> - </li> - <li > - <a href="/money/property">Property</a> - </li> - <li > - <a href="/money/money-matters">Money &amp; Me</a> - </li> - <li > - <a href="/money/superannuation">Superannuation</a> - </li> - <li > - <a href="/money/investing">Investing</a> - </li> - <li > - <a href="/money/guides-tools">Guides &amp; Tools</a> - </li> - <li > - <a href="http://blogs.news.com.au/moneystuff/index.php/news/blogs_overview">Blogs</a> - </li> - <li class=" last"> - <a href="/money/interest-rates">Interest Rates</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-entertainment "> - <a href="/entertainment">Entertainment</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="/entertainment/celebrity">Celebrity</a> - </li> - <li > - <a href="/entertainment/movies">Movies</a> - </li> - <li > - <a href="/entertainment/television">TV</a> - </li> - <li > - <a href="/entertainment/music">Music</a> - </li> - <li > - <a href="/entertainment/fashion">Fashion</a> - </li> - <li > - <a href="/entertainment/horoscopes">Horoscopes</a> - </li> - <li > - <a href="/entertainment/body-soul">body+soul</a> - </li> - <li > - <a href="/entertainment/puzzles">Puzzles</a> - </li> - <li class=" last"> - <a href="/entertainment/multimedia">Multimedia</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-travel "> - <a href="/travel">Travel</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="/travel/news">Travel News</a> - </li> - <li > - <a href="/travel/australia">Australia</a> - </li> - <li > - <a href="/travel/world">World</a> - </li> - <li > - <a href="/travel/holiday-ideas">Holiday Ideas</a> - </li> - <li > - <a href="http://holidaydeals.news.com.au/">Holiday Deals</a> - </li> - <li > - <a href="http://travelactivities.news.com.au/">Activities</a> - </li> - <li > - <a href="/travel/travel-advice">Travel Advice</a> - </li> - <li class=" last"> - <a href="/travel/galleries">Galleries</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-technology "> - <a href="/technology">Technology</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="/technology">Tech News</a> - </li> - <li > - <a href="/technology/reviews">Reviews</a> - </li> - <li > - <a href="/technology/guides/buying-a-laptop-beginners-guide/story-e6frfrui-1111118744566">Guides</a> - </li> - <li > - <a href="/technology/features">Features</a> - </li> - <li > - <a href="http://blogs.news.com.au/techblog/">Tech Blogs</a> - </li> - <li > - <a href="http://www.careerone.com.au/it-telecommunications-jobs">IT Jobs</a> - </li> - <li class=" last"> - <a href="/technology/galleries">Galleries</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-blog "> - <a href="/blogs">Blogs </a> - </li> - <li class="nav-video "> - <a href="http://player.video.news.com.au/news/">Video </a> - </li> - <li class="nav-australian-it "> - <a href="http://www.australianit.news.com.au/">AUSTRALIAN IT </a> - </li> - <li class="nav-fox-sports "> - <a href="http://www.foxsports.com.au/">FOX SPORTS</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="http://www.foxsports.com.au/afl/">AFL</a> - </li> - <li > - <a href="http://www.foxsports.com.au/league/">NRL</a> - </li> - <li > - <a href="http://www.foxsports.com.au/rugby/">Rugby</a> - </li> - <li > - <a href="http://www.foxsports.com.au/football/">Football</a> - </li> - <li class=" last"> - <a href="http://www.foxsports.com.au/cricket/">Cricket</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-classifieds "> - <a href="http://www.news.com.au/classifieds/">CLASSIFIEDS</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="http://www.carsguide.com.au/">Cars</a> - </li> - <li > - <a href="http://www.careerone.com.au/">Jobs</a> - </li> - <li > - <a href="http://www.realestate.com.au/">Real Estate</a> - </li> - <li > - <a href="http://www.truelocal.com.au/">Business Directory</a> - </li> - <li > - <a href="http://www.getprice.com.au/">Get Price Shopping</a> - </li> - <li > - <a href="http://www.wego.com/">Wego Travel Deals</a> - </li> - <li > - <a href="http://classifieds.news.com.au/">Other Classifieds</a> - </li> - <li class=" last"> - <a href="http://www.news.com.au/advertise/placemyad/">Place an Ad</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - <li class="nav-news-network last"> - <a href="/network">NEWS NETWORK</a> - <ul class="nav-list tier-2"> - <li class=" first "> - <a href="http://www.news.com.au/">news.com.au</a> - </li> - <li > - <a href="http://www.theaustralian.com.au/">The Australian</a> - </li> - <li > - <a href="http://www.dailytelegraph.com.au/">The Daily Telegraph</a> - </li> - <li > - <a href="http://www.couriermail.com.au/">The Courier-Mail</a> - </li> - <li > - <a href="http://www.heraldsun.com.au/">Herald Sun</a> - </li> - <li > - <a href="http://www.adelaidenow.com.au/">AdelaideNow</a> - </li> - <li > - <a href="http://www.perthnow.com.au/">PerthNow</a> - </li> - <li > - <a href="http://www.themercury.com.au/">The Mercury</a> - </li> - <li > - <a href="http://www.ntnews.com.au/">NT News</a> - </li> - <li class=" last"> - <a href="/network">Community Papers</a> - </li> - </ul> <!-- // .tier-2 --> - </li> - </ul> <!-- // .tier-1 --> - - </li> <!-- // #nav-level1 --> - <li id="nav-level2"> - <ul class="nav-list tier-1"> - <li class=" first "> - <a href="/breaking-news">Breaking news</a> - </li> - <li > - <a href="/national">National</a> - </li> - <li > - <a href="/world">World</a> - </li> - <li > - <a href="/weird-true-freaky">Weird</a> - </li> - <li > - <a href="http://weather.news.com.au/index.jsp?site=news.com.au">Weather</a> - </li> - <li > - <a href="/multimedia">Multimedia</a> - </li> - <li class=" last"> - <a href="/pictures">Pictures</a> - </li> - </ul> <!-- // .tier-1 --> - - </li> <!-- // #nav-level2 --> - </ul><!-- // #nav-wrap --> - </div><!-- // # nav --> -<div id="info-bar"> - <div class="info-bar-datestamp"><span - class="info-bar-datestamp-label">Last updated: - </span> - <strong class="info-bar-datestamp-date"> - January 11, 2010 - </strong> - </div> - <!-- // .info-bar-datestamp --> - <div class="info-bar-search"> -<div id="site-search"> - <form id="site-search-form" class="media-search-input" method="get" action="http://search.news.com.au/search"> - <input type="hidden" name="us" value="ndmnews" /> - <input type="hidden" name="as" value="NEWS" /> - <label class="search-for" for="mediasearch-site-search-input">Search for:</label> - <input type="text" id="mediasearch-site-search-input" name="q" value="" /> - <input class="submit" type="submit" value="Search" /> - </form> -</div> - </div><!-- // .info-bar-search --> - <!-- Generated at Mon Jan 11 12:48:13 EST 2010 --> - <!-- Generated at Mon Jan 11 12:48:13 EST 2010 --> - <div class="info-bar-weather"> - <div class="slimline-weather"> - <p> - <span class="assistive">Weather: </span> - <a href="http://weather.news.com.au/popup2_fcast.jsp?site=newscomau&amp;twcid=9770" class="slimline-weather-link" rel="popup[330,420]"> - <span class="slimline-weather-icon increasing-sunshine"></span> - Sydney 21<abbr title="Degrees Celsius">&deg;C</abbr> - - - 27<abbr title="Degree Celsius">&deg;C</abbr> - </a>. - Fine. Increasing sunny periods. - </p> - </div> <!-- // .info-bar-weather --> - </div> - - -</div><!-- // #info-bar --> - <div id="content"> - <div id="content-2"> - <!-- [Group:1225745312357] on [fwprodcontent02.ni.news.com.au] @ [January 11, 2010 12:48PM] --> -<div class="group text-g-news-top-stories item-count-2"> - <div class="group-header"> - <h2 class="heading">news top stories</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-2"> - <!-- Multi-Promo --> -<div class="module multi-promo main-image ci-count-1 mpos-1 mrpos-2 first-image-316w308h text-m-news-home-multi-promo-main-image id1225792812540"> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-1"> - <!-- promo-splash-a --> -<div class="promo-block promo-splash-01 "> - <div class="promo-image"><a href="/national/love-the-beast-then-go-electric/story-e6frfkvr-1225818009800"><img src="http://resources2.news.com.au/images/2010/01/11/1225818/013914-david-swan.jpg" alt="David Swan" width="316" height="308" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="caption"><p>Love the beast but hate the petrol costs?&nbsp; Meet the men who were so sick of shelling out at the bowser they chose to spend thousands making their old cars new - kind of. </p> <span class="read-more"><a href="/national/love-the-beast-then-go-electric/story-e6frfkvr-1225818009800"> More</a></span></div><!-- // .caption --> - </div><!-- // .promo-inner --> -</div><!-- // .promo-splash.promo-splash-01 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> -</div><!-- // .module.multi-promo --> - - <div class="module breakingnews breaking-news sectionref-breaking-news collection mpos-2 mrpos-1 text-m-breaking-news id1225749030856" id="id1225749030856"> - <div class="module-header"> - <h3 class="heading"><a href="/breaking-news">Breaking news -</a></h3> - </div> - <!-- // .module-header --> - <div class="module-content "> - <!-- Placement generated for [1225749030856] on [fwprodcontent02.ni.news.com.au] @ [Mon Jan 11 12:48:54 EST 2010] with unknown deps --> - <ul class="breaking-news-list" > - <li class="lipos-1 lirpos-8"> - <span class="timestamp">12:27PM</span> - <a href="/business/breaking-news/australian-share-market-higher-at-noon/story-e6frfkur-1225818046556">Commodities push shares higher at noon</a> - </li> - <li class="lipos-2 lirpos-7"> - <span class="timestamp">12:18PM</span> - <a href="/breaking-news/crab-pot-blamed-as-seaplane-ditches/story-e6frfku0-1225818041402">Crab pot blamed for seaplane accident</a> - </li> - <li class="lipos-3 lirpos-6"> - <span class="timestamp">12:11PM</span> - <a href="/breaking-news/bushfire-breaks-out-in-catastrophic-national-park-zone/story-e6frfku0-1225818039095">Fire breaks out in &#039;catastrophic&#039; zone</a> - </li> - <li class="lipos-4 lirpos-5"> - <span class="timestamp">12:05PM</span> - <a href="/breaking-news/mp3-creator-backs-perfect-streams-focus-on-dumb-devices/story-e6frfku0-1225818037028">MP3 creator focuses on &#039;dumb&#039; devices</a> - </li> - <li class="lipos-5 lirpos-4"> - <span class="timestamp">11:48AM</span> - <a href="/breaking-news/robber-hides-in-see-through-plastic-bag/story-e6frfku0-1225818030535">Robber hides in see-through plastic bag</a> - </li> - <li class="lipos-6 lirpos-3"> - <span class="timestamp">11:40AM</span> - <a href="/business/breaking-news/surge-in-job-ads-as-economy-improves/story-e6frfkur-1225818027499">Big jump in number of job ads</a> - </li> - <li class="lipos-7 lirpos-2"> - <span class="timestamp">11:37AM</span> - <a href="/breaking-news/indian-media-restraint-calls-will-fall-on-deaf-ears/story-e6frfku0-1225818023418">Indian media unlikely to back off - Crean</a> - </li> - <li class="lipos-8 lirpos-1"> - <span class="timestamp">11:30AM</span> - <a href="/breaking-news/more-than-210-prisoners-moved-for-mel-gibson-film/story-e6frfku0-1225818020780">Prisoners booted out for Mel Gibson film</a> - </li> - </ul> <!-- // .breaking-news-list --> - </div> <!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links mpos-2 mrpos-1 tier-1"> - <li > - <a href="/breaking-news">More breaking news</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .module-footer --> - </div> <!-- // .module .collection --> - - </div><!-- // .item ipos-1 irpos-2 --> - <div class="item ipos-2 irpos-1"> - <div class="module promo-story sectionref-newscomau-top-stories collection mpos-1 mrpos-1 text-m-news-home-plmt-top-stories id1225745323574" id="id1225745323574"> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-5 id1225817906863"> - <h4 class="heading"> - <a href="/world/drug-prisoners-bid-to-save-bali-nine-member/story-e6frfkyi-1225817906863"> - Bali 'confession' to help mate - </a> - </h4> - <a href="/world/drug-prisoners-bid-to-save-bali-nine-member/story-e6frfkyi-1225817906863" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225818/000864-news-image-rush-lawrence-20100111.jpg" alt="Scott Rush and Renae Lawrence" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - BALI Nine drug mule Renae Lawrence has taken a dramatic step to try to save Scott Rush's life. - </p> <!-- // .standfirst --> - <ul class="related"> - <li class="video first last lipos-1"><a href="http://player.video.news.com.au/news/?1382123840"><strong class="kicker">Video:</strong> Death row deal in Bali </a></li> - </ul> - </div> <!-- // .story-block pos-1 rpos-5 id1225817906863 --> - <div class="story-block sbpos-2 sbrpos-4 id1225817926398"> - <h4 class="heading"> - <a href="/national/states-brace-for-catastrophic-conditions/story-e6frfkw0-1225817926398"> - Fire breaks out in 'catastrophic fire zone' - </a> - </h4> - <a href="/national/states-brace-for-catastrophic-conditions/story-e6frfkw0-1225817926398" class="thumb-link"><img src="http://resources0.news.com.au/images/2009/12/30/1225814/558636-fire.jpg" alt="Fire" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - A NEWLY-spotted blaze has led to a state issuing its first code red catastrophic alert. - </p> <!-- // .standfirst --> - <ul class="related"> - <li class="story first last lipos-1"><a href="/national/brothers-swept-away-in-red-centre-flood/story-e6frfkvr-1225817903440"><strong class="kicker">Floods:</strong> Brothers swept away </a></li> - </ul> - </div> <!-- // .story-block pos-2 rpos-4 id1225817926398 --> - <div class="story-block sbpos-3 sbrpos-3 id1225817934483"> - <h4 class="heading"> - <a href="/national/farm-hunger-striker-peter-spencer-faces-eviction/story-e6frfkwi-1225817934483"> - Hunger striker 'shrinking away to nothing' - </a> - </h4> - <a href="/national/farm-hunger-striker-peter-spencer-faces-eviction/story-e6frfkwi-1225817934483" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/08/1225817/187464-news-image-spencer-20100108.jpg" alt="news image spencer 20100108" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - A FARMER who hasn't eaten for 50 days has broken down, saying he could do with a &quot;nice bit of steak&quot;. - </p> <!-- // .standfirst --> - <p class="comments"> - <a href="/national/farm-hunger-striker-peter-spencer-faces-eviction/comments-e6frfkwi-1225817934483">33 comments on this story</a> - </p> - <ul class="related"> - <li class="page first last lipos-1"><a href="http://www.dailytelegraph.com.au/news/opinion/harming-a-cause-with-a-thoughtless-stupid-stunt/story-e6frezz0-1225817856732"><strong class="kicker">Hunger strike:</strong> &#039;A stupid stunt&#039; </a></li> - </ul> - </div> <!-- // .story-block pos-3 rpos-3 id1225817934483 --> - <div class="story-block sbpos-4 sbrpos-2 id1225817992123"> - <h4 class="heading"> - <a href="/features/girl-weeps-blood-bleeds-through-intact-skin/story-e6frfl49-1225817992123"> - Girl weeps blood 'up to 50 times a day' - </a> - </h4> - <a href="/features/girl-weeps-blood-bleeds-through-intact-skin/story-e6frfl49-1225817992123" class="thumb-link"><img src="http://resources3.news.com.au/images/2010/01/11/1225818/028655-twinkle.jpg" alt="Twinkle" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - &nbsp;A GIRL who bleeds from her eyes and other parts of her body without any injury has left doctors baffled. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-4 rpos-2 id1225817992123 --> - <div class="story-block sbpos-5 sbrpos-1 id1225817967276"> - <h4 class="heading"> - <a href="/entertainment/celebrity/ricki-lee-coulter-says-women-dont-relate-to-nude-images-of-jennifer-hawkins/story-e6frfmqi-1225817967276"> - Ricki-Lee says women don't relate to Jen - </a> - </h4> - <a href="/entertainment/celebrity/ricki-lee-coulter-says-women-dont-relate-to-nude-images-of-jennifer-hawkins/story-e6frfmqi-1225817967276" class="thumb-link"><img src="http://resources1.news.com.au/images/2010/01/11/1225817/961457-ricki-lee-coulter.jpg" alt="Ricki Lee Coulter" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - THE singer says there is &ldquo;a big divide&rdquo; between the model and the average Australian woman. - </p> <!-- // .standfirst --> - <ul class="related"> - <li class="story first last lipos-1"><a href="/entertainment/music/austereos-jackie-o-breaks-her-silence-on-radio-stunt/story-e6frfn09-1225817871923"><strong class="kicker">Celebs:</strong> Jackie O&#039;s so sorry </a></li> - </ul> - </div> <!-- // .story-block pos-5 rpos-1 id1225817967276 --> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - </div><!-- // .item ipos-2 irpos-1 --> - </div><!-- // .group-content.item-count-2 --> -</div><!-- // .group --> - - <!-- [Group:1225745663158] on [fwprodcontent05.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group text-g-news-home-group-scrollo item-count-1"> - <div class="group-content"> - <div class="item ipos-1 irpos-1"> - <!-- Multi-Promo --> -<div class="module multi-promo ci-count-4 mpos-1 mrpos-1 first-image-152w86h text-m-news-home-multi-promo-scrollo id1225745655695"> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-4"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/entertainment/celebrity/lindsay-lohan-involved-in-hit-and-run-incident-in-los-angeles/story-e6frfmqi-1225817933341"><img src="http://resources2.news.com.au/images/2010/01/11/1225817/936762-lindsay-lohan.jpg" alt="Lindsay Lohan" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/entertainment/celebrity/lindsay-lohan-involved-in-hit-and-run-incident-in-los-angeles/story-e6frfmqi-1225817933341">Hit and run</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Lindsay Lohan's car hit a photographer, reports say<br /> -<img alt="" src="http://network.news.com.au/images/i_related.gif" /> <a href="http://www.news.com.au/entertainment/celebrity/lesbian-tila-tequila-tells-twitter-she-may-be-pregnant-after-losing-casey-johnson/story-e6frfmqi-1225817933182">Tila hints at Casey's baby</a><br /> -&nbsp;</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-2 cirpos-3"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 best-blogs"> - <div class="promo-image"><a href="http://www.news.com.au/business/rags-to-riches-tale-as-dotcom-geek-snaps-up-top-harbour-properties/story-e6frfm1i-1225817935527"><img src="http://resources3.news.com.au/images/2010/01/11/1225817/938127-simon-clausen.jpg" alt="Simon Clausen" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="http://www.news.com.au/business/rags-to-riches-tale-as-dotcom-geek-snaps-up-top-harbour-properties/story-e6frfm1i-1225817935527">Geek to chic</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><div class="promo-text">Meet the geek-turned-property-tycoon building an empire of havens for the super-rich -<p>&nbsp;</p> -</div> -<!-- // .promo-text --> <!-- // .promo-inner --> <!-- // .promo-block.promo-block-03 --> <!-- // .content-item --> <!-- // .module-content --> <!-- // .module.multi-promo --> <!-- // .item ipos-1 irpos-1 --> <!-- // .group-content.item-count-1 --> -<p>&nbsp;</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-3 cirpos-2"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/entertainment/celebrity/gallery-e6frfmr0-1225818037102"><img src="http://resources3.news.com.au/images/2010/01/11/1225818/037387-victoria-beckham.jpg" alt="Victoria Beckham" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/entertainment/celebrity/gallery-e6frfmr0-1225818037102">ShowBUZZ</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>What Posh took home from an $865,000 shopping spree <br /> -<img width="11" height="12" src="http://www.news.com.au/images/icon_galleries.gif" alt="Gallery" /> <a href="http://www.news.com.au/entertainment/celebrity/gallery-e6frfmr9-1111120736498">Tila Tequila's celeb catfight </a><br /> -&nbsp;</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-4 cirpos-1"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/travel/news/the-white-cockatoo-resort-up-for-sale/story-e6frfq80-1225817882094"><img src="http://resources1.news.com.au/images/2010/01/11/1225817/931741-white-cockatoo.jpg" alt="White Cockatoo" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/travel/news/the-white-cockatoo-resort-up-for-sale/story-e6frfq80-1225817882094">Hot property</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Australia's unofficial top sex tourist destination now comes with a million-dollar price tag</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> -</div><!-- // .module.multi-promo --> - - </div><!-- // .item ipos-1 irpos-1 --> - </div><!-- // .group-content.item-count-1 --> -</div><!-- // .group --> - - </div><!-- // #content-2 --> - <div id="content-3"> - <!-- [Group:1225750357522] on [fwprodcontent02.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group text-g-news-home-group-rhc item-count-3"> - <div class="group-content"> - <div class="item ipos-1 irpos-3"> - <!-- Generated at Mon Jan 11 12:49:21 EST 2010 --> -<div class="module weather-tab mpos-1 mrpos-9 id1225750362917"> - <div class="module-header"> - </div> - <div class="module-content"> - <div class="more-cities js-popmenu-parent"> More Cities - <ul class="js-tabs nav-submenu"> - <li class="js-tab" title="S00" > - <a href="S00">Adelaide</a> - </li> - <li class="js-tab" title="Q00" > - <a href="Q00">Brisbane</a> - </li> - <li class="js-tab" title="Q04" > - <a href="Q04">Cairns</a> - </li> - <li class="js-tab" title="N07" > - <a href="N07">Canberra</a> - </li> - <li class="js-tab" title="Q15" > - <a href="Q15">Coolangatta</a> - </li> - <li class="js-tab" title="D00" > - <a href="D00">Darwin</a> - </li> - <li class="js-tab" title="T04" > - <a href="T04">Hobart</a> - </li> - <li class="js-tab" title="V00" > - <a href="V00">Melbourne</a> - </li> - <li class="js-tab" title="N03" > - <a href="N03">Newcastle</a> - </li> - <li class="js-tab" title="W00" > - <a href="W00">Perth</a> - </li> - <li class="js-tab" title="N00" > - <a href="N00">Sydney</a> - </li> - <li class="js-tab" title="S00" > - <a href="S00">Adelaide</a> - </li> - </ul> - </div><!-- // .more-cities .js-popmenu-parent --> - <div class="js-tab-content S00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=S00"> - <span>Adelaide</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="showers">Showers</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">24&deg;C - 26&deg;C</span> - <span class="weather-today-desc">Cloudy. Showers.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">21&deg;C - 31&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=S00&amp;twcid=9401">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .S00 --> - <div class="js-tab-content Q00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=Q00"> - <span>Brisbane</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">21&deg;C - 31&deg;C</span> - <span class="weather-today-desc">Fine.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="showers">Showers</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">24&deg;C - 31&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=Q00&amp;twcid=9250">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .Q00 --> - <div class="js-tab-content Q04"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=Q04"> - <span>Cairns</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="showers">Showers</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">24&deg;C - 31&deg;C</span> - <span class="weather-today-desc">Showers.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="sunny">Sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">16&deg;C - 38&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=Q04&amp;twcid=9186">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .Q04 --> - <div class="js-tab-content N07"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=N07"> - <span>Canberra</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">20&deg;C - 38&deg;C</span> - <span class="weather-today-desc">Fine, mostly sunny.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">20&deg;C - 30&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=N07&amp;twcid=9028">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .N07 --> - <div class="js-tab-content Q15"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=Q15"> - <span>Coolangatta</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">21&deg;C - 30&deg;C</span> - <span class="weather-today-desc">Fine.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="late-thunder">Late thunder</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">25&deg;C - 32&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=Q15&amp;twcid=9254">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .Q15 --> - <div class="js-tab-content D00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=D00"> - <span>Darwin</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="thunderstorms">Thunderstorms</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">25&deg;C - 31&deg;C</span> - <span class="weather-today-desc">Showers or storms.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">14&deg;C - 36&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=D00&amp;twcid=9110">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .D00 --> - <div class="js-tab-content T04"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=T04"> - <span>Hobart</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="rain-clearing">Rain clearing</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">20&deg;C - 28&deg;C</span> - <span class="weather-today-desc">A little rain, clearing later.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">21&deg;C - 43&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=T04&amp;twcid=9329">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .T04 --> - <div class="js-tab-content V00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=V00"> - <span>Melbourne</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="showers">Showers</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">27&deg;C - 33&deg;C</span> - <span class="weather-today-desc">Cool change. Few showers.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="increasing-sunshine">Increasing sunshine</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">21&deg;C - 28&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=V00&amp;twcid=9477">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .V00 --> - <div class="js-tab-content N03"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=N03"> - <span>Newcastle</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">21&deg;C - 34&deg;C</span> - <span class="weather-today-desc">Fine.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-sunny">Mostly sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">13&deg;C - 24&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=N03&amp;twcid=8973">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .N03 --> - <div class="js-tab-content W00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=W00"> - <span>Perth</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="sunny">Sunny</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">12&deg;C - 30&deg;C</span> - <span class="weather-today-desc">Sunny.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="increasing-sunshine">Increasing sunshine</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">21&deg;C - 27&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=W00&amp;twcid=9528">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .W00 --> - <div class="js-tab-content N00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=N00"> - <span>Sydney</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="cloud-increasing">Cloud increasing</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">22&deg;C - 33&deg;C</span> - <span class="weather-today-desc">Fine. Cloud increasing.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-cloudy">Mostly cloudy</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">24&deg;C - 32&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=N00&amp;twcid=9770">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .N00 --> - <div class="js-tab-content S00"> - <h2 class="heading"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&contexttype=district&contextcode=S00"> - <span>Adelaide</span> - </a> - </h2> - <div class="weather-today"> - <div class="weathericon-large"> - <span class="windy">Windy</span> - </div><!-- // .weathericon-large --> - <span class="weather-today-temp">29&deg;C - 42&deg;C</span> - <span class="weather-today-desc">Dry and windy.</span> - </div><!-- // .weather-today --> - <div class="weather-forecast"> - <div class="weathericon-large"> - <span class="mostly-cloudy">Mostly cloudy</span> - </div><!-- // .weathericon-large --> - <span class="weather-tomorrow">Tomorrow</span> - <span class="weather-forecast-temp">24&deg;C - 32&deg;C</span> - </div><!-- // .weather-forecast --> - <div class="weather-links"> - <span class="view-forecast"> - <a href="http://weather.news.com.au/index.jsp?site=news.com.au&amp;contexttype=district&amp;contextcode=S00&amp;twcid=9401">View 5 Day Forecast</a> - </span> - <span class="view-weather-page"><a href="http://weather.news.com.au/index.jsp?site=news.com.au">Full Weather Section</a></span> - </div><!-- // .weather-links --> - </div><!-- // .js-tab-content .S00 --> - </div><!-- // .module-content --> -</div><!-- // .module .weather-tab . --> - - - <div class="custom-html "> - <script type="text/javascript"> -if(ndm.load && ndm.load.newshome && ndm.load.newshome.weather) { - ndm.load.newshome.weather(); -} -</script> - </div><!-- // .custom-html --> - - <div class="ad ad-island mpos-3 mrpos-7" id="ad-island-" > - <div class="ndmadkit ndmadkit-island"><script type="text/javascript">ndm.kit.island();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-island"><script type="text/javascript">ndm.kit.island();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-island"><script type="text/javascript">ndm.kit.island();</script></div><!-- // .ndmadkit --> - </div><!-- // .ad .ad-island- --> - - <!-- Generated at Mon Jan 11 12:49:21 EST 2010 --> -<div class="module most-popular tabbed js-tabbed mpos-4 mrpos-6 id1225749298340"> - <div class="module-header"> - <h3 class="heading"> - <a href="/most-popular">Most Popular</a> - </h3> - </div><!-- // .module-header --> - <div class="module-controls"> - <ul class="tab-set"> - <li class="tab js-tab"><a href="#">News</a></li> - <li class="tab js-tab"><a href="#">Blogs</a></li> - </ul> - </div> - <div class="module-content"> - <div class="content-item tab-content js-tab-content"> - <ol> - <li class="lipos-1 lirpos-10"><a href="http://www.news.com.au/story/0,1,26573121-23109,00.html">'Crime lord's' penis falls off in raid</a></li> - <li class="lipos-2 lirpos-9"><a href="http://www.news.com.au/story/0,1,26574510-401,00.html">Bali 'confession' to help mate</a></li> - <li class="lipos-3 lirpos-8"><a href="http://www.news.com.au/story/0,1,26574277-421,00.html">Teens mutilated by botched circumcisions </a></li> - <li class="lipos-4 lirpos-7"><a href="http://www.news.com.au/travel/story/0,1,26573967-5014090,00.html">Sex resort starts new life with a bang </a></li> - <li class="lipos-5 lirpos-6"><a href="http://www.news.com.au/entertainment/story/0,1,26573657-7484,00.html">Jackie O's so sorry for radio show stunt </a></li> - <li class="lipos-6 lirpos-5"><a href="http://www.news.com.au/entertainment/story/0,1,26574603-5013560,00.html">Spears 'catches lover with two women'</a></li> - <li class="lipos-7 lirpos-4"><a href="http://www.news.com.au/entertainment/story/0,1,26574711-5013560,00.html">Ricki-Lee says women don't relate to Jen</a></li> - <li class="lipos-8 lirpos-3"><a href="http://www.news.com.au/business/story/0,1,26574546-462,00.html">Australia's new leading state revealed </a></li> - <li class="lipos-9 lirpos-2"><a href="http://www.news.com.au/technology/story/0,1,26572369-5014239,00.html">World's first life-size robotic girlfriend</a></li> - <li class="lipos-10 lirpos-1"><a href="http://www.news.com.au/business/story/0,1,26573967-462,00.html">Sex resort starts new life with a bang </a></li> - </ol> - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content"> - <ol> - <li class="lipos-1 lirpos-10"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/what_should_i_do_about_my_friend_the_home_wrecker">What should I do about my friend the home wrecker?</a></li> - <li class="lipos-2 lirpos-9"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/i_did_a_bad_thing_at_the_office_xmas_party">I did a bad thing at the office xmas party</a></li> - <li class="lipos-3 lirpos-8"><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/the_mmm_monday_mourning_marvel4">The M.M.M (Monday Mourning Marvel)</a></li> - <li class="lipos-4 lirpos-7"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/how_can_i_get_him_to_work_on_his_technique">How can I get him to work on his technique?</a></li> - <li class="lipos-5 lirpos-6"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/i_had_sex_with_my_mates_16_year_old_daughter">I had sex with my friend&#8217;s 16-year-old daughter. What should I do?</a></li> - <li class="lipos-6 lirpos-5"><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/what_is_a_self_potato">What is a self-potato?</a></li> - <li class="lipos-7 lirpos-4"><a href="http://blogs.news.com.au/subpub/index.php/news/comments/once_and_future_prints">Tomorrow&#8217;s news</a></li> - <li class="lipos-8 lirpos-3"><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/the_most_beautiful_women_in_the_world">The most beautiful men and women in the world</a></li> - <li class="lipos-9 lirpos-2"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/has_my_girlfriend_slept_with_too_many_men">Has my girlfriend slept with too many men?</a></li> - <li class="lipos-10 lirpos-1"><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/people_do_like_to_complain_dont_they">People do like to complain, don&#8217;t they?</a></li> - </ol> - </div><!-- // .content-item .js-tab-content --> - </div><!-- // .module-content --> - <div class="module-footer"> - <p class="more-link"><a href="/most-popular">More most popular</a></p> - </div><!-- // .module-footer --> -</div><!-- // .module .most-popular .news nmd most pop tabs --> - - - <div class="module summary-fader story-fader js-story-fader ci-count-6 collection mpos-5 mrpos-5 text-m-wtf-weird-true-freakynbsp id1225750779215" id="id1225750779215"> - <div class="module-header"> - <h3 class="heading"><a href="/weird-true-freaky">WTF: Weird True Freaky&nbsp;</a></h3> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="content-item fader-item js-fader-item cipos-1 cirpos-6"> - <div class="story-block sbpos-1 sbrpos-6 id1225817609646"> - <h4 class="heading"> - <a href="/weird-true-freaky/enemies-unite-for-world-record-hummus/story-e6frflri-1225817609646"> - Giant hummus in name of world peas - </a> - </h4> - <a href="/weird-true-freaky/enemies-unite-for-world-record-hummus/story-e6frflri-1225817609646" class="thumb-link"><img src="http://resources3.news.com.au/images/2010/01/09/1225817/608639-hummus-world-record.jpg" alt="hummus world record" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - ISRAELI Arab and Jewish chefs have put aside their differences to serve up a world record dish of hummus. - </p> <!-- // .standfirst --> - - </div> <!-- // .story-block --> - </div> <!-- // .content-item --> - <div class="content-item fader-item js-fader-item cipos-2 cirpos-5"> - <div class="story-block sbpos-2 sbrpos-5 id1225818036189"> - <h4 class="heading"> - <a href="/weird-true-freaky/robber-hides-in-see-through-plastic-bag/story-e6frflri-1225818036189"> - Robber hides in see-through plastic bag - </a> - </h4> - <a href="/weird-true-freaky/robber-hides-in-see-through-plastic-bag/story-e6frflri-1225818036189" class="thumb-link"><img src="http://resources1.news.com.au/images/2010/01/11/1225818/037657-plastic-bag.jpg" alt="plastic bag" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - A ROBBER&nbsp;wearing a transparent plastic bag over his head has held up a service station. - </p> <!-- // .standfirst --> - - </div> <!-- // .story-block --> - </div> <!-- // .content-item --> - <div class="content-item fader-item js-fader-item cipos-3 cirpos-4"> - <div class="story-block sbpos-3 sbrpos-4 id1225817189755"> - <h4 class="heading"> - <a href="/weird-true-freaky/swiss-millionaire-conman-slapped-with-315000-speeding-ticket/story-e6frflri-1225817189755"> - Speeding millionaire cops $315,000 fine - </a> - </h4> - <a href="/weird-true-freaky/swiss-millionaire-conman-slapped-with-315000-speeding-ticket/story-e6frflri-1225817189755" class="thumb-link"><img src="http://resources1.news.com.au/images/2010/01/08/1225817/189861-news-image-ferrari-20100108.jpg" alt="news image ferrari 20100108" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - A MULTI-millionaire was fined a staggering $315,000 for trying to evade a hefty speeding fine. - </p> <!-- // .standfirst --> - - </div> <!-- // .story-block --> - </div> <!-- // .content-item --> - <div class="content-item fader-item js-fader-item cipos-4 cirpos-3"> - <div class="story-block sbpos-4 sbrpos-3 id1225816530181"> - <h4 class="heading"> - <a href="/weird-true-freaky/man-wishes-wife-happy-birthday-in-manure/story-e6frflri-1225816530181"> - Man's Happy Birthday wish - in manure - </a> - </h4> - <a href="/weird-true-freaky/man-wishes-wife-happy-birthday-in-manure/story-e6frflri-1225816530181" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/06/1225816/532432-manure.jpg" alt="Manure" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - ASK one wife if she got the perfect birthday present and she'll tell you her hubby &quot;dung good&quot;. - </p> <!-- // .standfirst --> - - </div> <!-- // .story-block --> - </div> <!-- // .content-item --> - <div class="content-item fader-item js-fader-item cipos-5 cirpos-2"> - <div class="story-block sbpos-5 sbrpos-2 id1225816221204"> - <h4 class="heading"> - <a href="/weird-true-freaky/machine-is-a-real-turn-off-literally/story-e6frflri-1225816221204"> - Machine is a real turn-off - literally - </a> - </h4> - <a href="/weird-true-freaky/machine-is-a-real-turn-off-literally/story-e6frflri-1225816221204" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/05/1225816/221548-machine.jpg" alt="Machine" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - IT'S made of wood and has a switch on top. That's it really. So what is the appeal of this box? - </p> <!-- // .standfirst --> - - </div> <!-- // .story-block --> - </div> <!-- // .content-item --> - <div class="content-item fader-item js-fader-item cipos-6 cirpos-1"> - <div class="story-block sbpos-6 sbrpos-1 id1225815914408"> - <h4 class="heading"> - <a href="/weird-true-freaky/bungling-bank-robbers-top-darwin-awards/story-e6frflri-1225815914408"> - Bungling bank robbers top Darwin Awards - </a> - </h4> - <a href="/weird-true-freaky/bungling-bank-robbers-top-darwin-awards/story-e6frflri-1225815914408" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/04/1225815/919132-atm.jpg" alt="ATM" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - TWO robbers who blew themselves up trying to make an ATM &quot;withdrawal&quot; win awards. - </p> <!-- // .standfirst --> - - </div> <!-- // .story-block --> - </div> <!-- // .content-item --> - </div> <!-- // .module-content --> - <div class="module-controls fader-controls js-fader-controls"> - <p class="fader-prev-button js-fader-prev"><a href="#">Prev</a></p> - <p class="fader-counter-container"><span class="fader-counter js-fader-counter">1</span>&nbsp;of&nbsp;6</p> - <p class="fader-next-button js-fader-next"><a href="#">Next</a></p> - </div><!-- // .module-controls .js-fader-controls --> - <div class="module-footer"> - <ul class="more-links mpos-5 mrpos-5 tier-1"> - <li > - <a href="/weird-true-freaky">More WTF stories</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .module-footer --> - </div> <!-- // .module .collection --> - - <!-- START VCMS video promo widget embed --> -<div class="module video-embed vcms-player vcms-promo-widget video-widget-large" > - <div class="module-header"> - <h2 class="heading"><a href="http://player.video.news.com.au/news/">Latest video</a></h2> - </div> - <div class="module-content" id="vcms-1225750964297"> - <noscript> - <div class="caption"> - <p>[To view video please enable JavaScript and Flash.]</p></div> - </noscript> - </div><!-- // .module-content --> -</div><!-- // .module.video-embed.vcms-player --> -<script type="text/javascript"> - ndm.media.embedWidget("http://static.video.news.com.au/widget/config/news/news-home/definition.json","vcms-1225750964297"); -</script> -<!-- END VCMS video promo widget embed --> - - <!-- Multi-Promo --> -<div class="module multi-promo ci-count-5 mpos-7 mrpos-3 first-image-152w86h text-m-blogroll id1225750953740"> - <div class="module-header"> - <h3 class="heading"><a href="/blogs">Blogroll</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-5"> - <!-- promo-block-b --> - <div class="promo-block promo-block-04 "> - <div class="promo-image"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/i_punched_my_flat_mate_now_everyone_hates_me/"><img src="http://resources0.news.com.au/images/2009/12/02/1225806/231572-ask-bossy-kate-de-brito.jpg" alt="Ask Bossy, Kate de Brito" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="http://blogs.news.com.au/bossy/index.php/news/comments/i_punched_my_flat_mate_now_everyone_hates_me/">Ask Bossy</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>I gave&nbsp; my flatmate a shiner. Now I'm sad, porking up and everyone hates me.</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-04 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-2 cirpos-4"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://blogs.news.com.au/subpub/index.php/news/comments/once_and_future_prints/">Sub in the Pub: Headlines you want to see</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-3 cirpos-3"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://blogs.news.com.au/news/splat/index.php/news/comments/which_celebrity_can_you_imagine_having_a_bed_in_today/">Splat!: Which celebrity would bed-in these days?</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-4 cirpos-2"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://blogs.news.com.au/dailytelegraph/timblair/index.php/dailytelegraph/comments/australian_of_the_year1/">Pru Goward: One contender for Aussie of the Year</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-5 cirpos-1"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://blogs.news.com.au/couriermail/emily/index.php/couriermail/comments/a_pretty_pair_of_brand_new_shoes/">Emily Everywhere: Meeting Gloria in giant shoes</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="/blogs">More blogs</a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module.multi-promo --> - - <!-- Multi-Promo --> -<div class="module multi-promo ci-count-5 mpos-8 mrpos-2 first-image-100w75h text-m-opinion-from-the-punch id1225816197941"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.thepunch.com.au/">Opinion from The Punch</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-5"> - <!-- promo-block-a --> -<div class="story-block"> - <h4 class="heading"> - <a href="http://www.thepunch.com.au/articles/what-happened-to-pollies-being-good-at-just-politics/">Why can&#039;t pollies just be good at politics?</a> - </h4> - <a href="http://www.thepunch.com.au/articles/what-happened-to-pollies-being-good-at-just-politics/" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225818/009700-putin.jpg" alt="putin" class="thumbnail" width="100" height="75" /></a> - <div class="standfirst"><p> -<link rel="File-List" href="file:///C:%5CDOCUME%7E1%5Ckippistl%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml" /><!--[if gte mso 9]><xml> -<w:WordDocument> -<w:View>Normal</w:View> -<w:Zoom>0</w:Zoom> -<w:PunctuationKerning /> -<w:ValidateAgainstSchemas /> -<w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> -<w:IgnoreMixedContent>false</w:IgnoreMixedContent> -<w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> -<w:Compatibility> -<w:BreakWrappedTables /> -<w:SnapToGridInCell /> -<w:WrapTextWithPunct /> -<w:UseAsianBreakRules /> -<w:DontGrowAutofit /> -</w:Compatibility> -<w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> -</w:WordDocument> -</xml><![endif]--><!--[if gte mso 9]><xml> -<w:LatentStyles DefLockedState="false" LatentStyleCount="156"> -</w:LatentStyles> -</xml><![endif]--><style type="text/css"> -<!-- - /* Style Definitions */ - p.MsoNormal, li.MsoNormal, div.MsoNormal - {mso-style-parent:""; - margin:0cm; - margin-bottom:.0001pt; - mso-pagination:widow-orphan; - font-size:12.0pt; - font-family:"Times New Roman"; - mso-fareast-font-family:"Times New Roman";} -span.EmailStyle15 - {mso-style-type:personal; - mso-style-noshow:yes; - mso-ansi-font-size:10.0pt; - mso-bidi-font-size:10.0pt; - font-family:Arial; - mso-ascii-font-family:Arial; - mso-hansi-font-family:Arial; - mso-bidi-font-family:Arial; - color:windowtext;} -@page Section1 - {size:595.3pt 841.9pt; - margin:72.0pt 90.0pt 72.0pt 90.0pt; - mso-header-margin:35.4pt; - mso-footer-margin:35.4pt; - mso-paper-source:0;} -div.Section1 - {page:Section1;} ---> -</style><!--[if gte mso 10]> -<style> -/* Style Definitions */ -table.MsoNormalTable -{mso-style-name:"Table Normal"; -mso-tstyle-rowband-size:0; -mso-tstyle-colband-size:0; -mso-style-noshow:yes; -mso-style-parent:""; -mso-padding-alt:0cm 5.4pt 0cm 5.4pt; -mso-para-margin:0cm; -mso-para-margin-bottom:.0001pt; -mso-pagination:widow-orphan; -font-size:10.0pt; -font-family:"Times New Roman"; -mso-ansi-language:#0400; -mso-fareast-language:#0400; -mso-bidi-language:#0400;} -</style> -<![endif]--></p> -<p class="MsoNormal"><span style="font-size: 10pt; font-family: Arial;">PAUL Colgan's all for politicians having hobbies but they make the rest of us feel like we&rsquo;re in for a lifetime of mediocrity.<o:p></o:p></span></p> -<p>&nbsp;</p></div> -</div><!-- // .story-block --> - - </div><!-- // .content-item --> - <div class="content-item cipos-2 cirpos-4"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://www.thepunch.com.au/articles/oh-hotmail-you-are-truly-the-fairest-of-them-all/">Leo Shanahan: Oh Hotmail the fairest of them all</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-3 cirpos-3"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://www.thepunch.com.au/articles/the-nine-biggest-sins-of-packaged-food/">Nola James: Nine sins of packaged food</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-4 cirpos-2"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://www.thepunch.com.au/articles/the-ethics-of-buying-a-goat/">Rhiannon Elston: The ethics of buying a goat</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-5 cirpos-1"> - <!-- promo-link-a --> - <div class="promo-block promo-link-01 promo-related-link"> - <a href="http://www.thepunch.com.au/articles/a-league-reasons-for-roundballers-to-love-branko-culina/">David Hall: Reasons roundballers love Branko</a> - </div><!-- // .promo-block.promo-link-01 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="http://www.thepunch.com.au/">More opinion from The Punch</a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module.multi-promo --> - - <!-- Generated at Mon Jan 11 12:49:25 EST 2010 --> -<!-- sign capricorn --> -<!-- dateString1 110110-[au.com.ndm.common.nonmanaged.rssparser.Horoscope@4807b339] --> -<!-- dateString2 110110-[au.com.ndm.common.nonmanaged.rssparser.Horoscope@4807b339] --> - <div class="module horoscope-summary mpos-9 mrpos-1 id1225750965142"> - <div class="module-header"> - <h3 class="heading"><a href="/entertainment/horoscopes" rel="track-horoscope-summary">Horoscopes</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> - <div class="story-block capricorn"> - <h4 class="heading"><a href="/entertainment/horoscopes">Capricorn</a></h4> - <p class="date-range">Dec 22 - Jan 20 -</p> -<div class="horoscope-sign"><a href="/entertainment/horoscopes" rel="track-horoscope-summary">capricorn</a></div> - <p class="stand-first"> -Good builders rarely clear up after themselves. It is, apparently, impossible to operate a vacuum cleaner if you know how to use a drill. But, then, they are not alone. Surgeo... <a href="/entertainment/horoscopes">Read more</a></p> - </div> <!-- // .story-block . --> - </div><!-- // .module-content --> - <div class="module-footer"> - <p class="powered-by">Powered by <strong>Jonathan Cainer</strong></p> - <p class="more-link"><a href="/entertainment/horoscopes" rel="track-horoscope-summary">Read All Horoscopes</a></p> - </div><!-- // .content-footer --> - </div><!-- // .module .horoscope-summary .news home horoscopes --> - - - </div><!-- // .item ipos-1 irpos-3 --> - <div class="item ipos-2 irpos-2"> - <div class="ad ad-shortrec mpos-1 mrpos-1" id="ad-shortrec" > - <div class="ndmadkit ndmadkit-shortrec"><script type="text/javascript">ndm.kit.shortrec();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-shortrec"><script type="text/javascript">ndm.kit.shortrec();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-shortrec"><script type="text/javascript">ndm.kit.shortrec();</script></div><!-- // .ndmadkit --> - </div><!-- // .ad .ad-shortrec --> - - </div><!-- // .item ipos-2 irpos-2 --> - <div class="item ipos-3 irpos-1"> - <div class="ad ad-shortrec mpos-1 mrpos-2" id="ad-shortrec" > - <div class="ndmadkit ndmadkit-shortrec"><script type="text/javascript">ndm.kit.shortrec();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-shortrec"><script type="text/javascript">ndm.kit.shortrec();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-shortrec"><script type="text/javascript">ndm.kit.shortrec();</script></div><!-- // .ndmadkit --> - </div><!-- // .ad .ad-shortrec --> - - <div class="ad ad-adsensemedium mpos-2 mrpos-1" id="ad-adsensemedium" > - <div class="ndmadkit ndmadkit-adsensemedium"><script type="text/javascript">ndm.kit.adsensemedium();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-adsensemedium"><script type="text/javascript">ndm.kit.adsensemedium();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-adsensemedium"><script type="text/javascript">ndm.kit.adsensemedium();</script></div><!-- // .ndmadkit --> - </div><!-- // .ad .ad-adsensemedium --> - - </div><!-- // .item ipos-3 irpos-1 --> - </div><!-- // .group-content.item-count-3 --> -</div><!-- // .group --> - - </div><!-- // #content-3 --> - <div id="content-4"> - <!-- [Group:1225745348045] on [fwprodcontent05.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group group-accordion text-g-national-amp-local item-count-1"> - <div class="group-header"> - <h2 class="heading">National & Local</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-1"> - <div class="module default collection mpos-1 mrpos-2 text-m-national id1225745360518" id="id1225745360518"> - <div class="module-header"> - <h3 class="heading"><a href="/national">National -</a></h3> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817945718"> - <h4 class="heading"> - <a href="/national/jaspreet-singhs-family-denies-race-attack-story-made-up/story-e6frfkvr-1225817945718"> - Family denies 'race attack' story made up - </a> - </h4> - <a href="/national/jaspreet-singhs-family-denies-race-attack-story-made-up/story-e6frfkvr-1225817945718" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225817/947564-jaspreet-singh.jpg" alt="Jaspreet Singh" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - FAMILY, friends of Indian man who alleges he was set on fire by four men reject rumours he made it up. - </p> <!-- // .standfirst --> - <ul class="related"> - <li class="story first last lipos-1"><a href="http://www.heraldsun.com.au/news/are-victorians-racist/story-e6frf7jo-1225817863179"><strong class="kicker">Herald Sun:</strong> Are Victorians racist? </a></li> - </ul> - </div> <!-- // .story-block pos-1 rpos-1 id1225817945718 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/national/casuarina-mum-six-times-over-limit-while-driving-kids/story-e6frfkvr-1225817936003"> Mum 'six times over limit while driving kids' </a></li> - <li class="story lipos-2"><a href="/national/japan-attacks-julia-gillards-stand-on-whaling-after-ady-gil-crash/story-e6frfkvr-1225817941811"> Japan 'provoked' by Gillard whaling stance </a></li> - <li class="story lipos-3"><a href="/national/trio-in-taren-point-armed-robbery-car-chase-carjacking/story-e6frfkvr-1225817939979"> Trio in armed robbery, car chase, carjacking </a></li> - <li class="story last lipos-4"><a href="/national/boy-three-drowns-in-dam-at-family-property-in-dimbulah/story-e6frfkvr-1225817933311"> Boy, three, drowns in dam at family property </a></li> - </ul> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - <div class="module tabbed js-tabbed tabbed-small ci-count-8 mpos-2 mrpos-1 text-m-news-home-national-tab-multi id1225791975067"> - <div class="module-controls"> - <ul class="tab-set"> - <li class="tab js-tab lipos-1 lirpos-8"><a href="#">Map</a></li> - <li class="tab js-tab lipos-2 lirpos-7"><a href="#">NSW &amp; ACT</a></li> - <li class="tab js-tab lipos-3 lirpos-6"><a href="#">Qld</a></li> - <li class="tab js-tab lipos-4 lirpos-5"><a href="#">Vic</a></li> - <li class="tab js-tab lipos-5 lirpos-4"><a href="#">SA</a></li> - <li class="tab js-tab lipos-6 lirpos-3"><a href="#">WA</a></li> - <li class="tab js-tab lipos-7 lirpos-2"><a href="#">NT</a></li> - <li class="tab js-tab lipos-8 lirpos-1"><a href="#">Tas</a></li> - </ul> - </div><!-- // .module-controls --> - <div class="module-content"> - <div class="content-item tab-content js-tab-content cipos-1 cirpos-8" > - <div class="custom-html "> - <div class="map national-map"> - <h3 class="heading">Select your state to view your local news here:</h3> - <img src="http://resources.news.com.au/cs/newscomau/custom-html/transparent.gif" width="190" height="175" border="0" id="around-aus-map-image" class="no-state" usemap="#around-aus-map" /> - <map name="around-aus-map" id="around-aus-map"> - <area shape="poly" coords="158,140,151,132,138,132,129,122,125,122,126,99,178,100" href="#around-australia-nswact" alt="nswact" /> - <area shape="poly" coords="115,38,114,82,127,82,126,96,177,98,178,84,163,59,152,51,146,29,136,9,127,43" href="#around-australia-qld" alt="qld" /> - <area shape="poly" coords="158,140,151,131,137,130,129,121,126,121,125,140,133,143,144,144" href="#around-australia-vic" alt="vic" /> - <area shape="poly" coords="126,82,124,139,94,111,78,109,77,83" href="#around-australia-sa" alt="sa" /> - <area shape="poly" coords="73,31,77,110,30,131,8,88,10,72,40,56,45,42,62,26" href="#around-australia-wa" alt="wa" /> - <area shape="poly" coords="114,38,114,81,77,82,74,32,81,14,109,12,105,29" href="#around-australia-nt" alt="nt" /> - <area shape="poly" coords="135,150,142,168,173,165,173,157" href="#around-australia-tas" alt="tas" /> - </map> -</div> - </div><!-- // .custom-html --> - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-2 cirpos-7" > - <div class="ci default state state-nswact collection text-m-nsw-act id1225749148204" id="id1225749148204"> - <div class="ci-header"> - <h3 class="heading"><a href="http://www.dailytelegraph.com.au/">NSW / ACT</a></h3> - <ul class="more-links tier-1"> - <li > - <a href="http://www.dailytelegraph.com.au/">The Daily Telegraph</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .ci-header --> - <div class="ci-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817868581"> - <h4 class="heading"> - <a href="http://www.dailytelegraph.com.au/news/brace-yourself-for-the-metro-meltdown/story-e6freuy9-1225817868581"> - Brace yourself for Metro meltdown - </a> - </h4> - <a href="http://www.dailytelegraph.com.au/news/brace-yourself-for-the-metro-meltdown/story-e6freuy9-1225817868581" class="thumb-link"><img src="http://resources2.news.com.au/images/2009/09/21/1225777/281594-traffic.jpg" alt="Traffic jam" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - TENS of thousands of Sydney commuters face transport chaos that will last years while the CBD Metro is built, the State Government has admitted. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225817868581 --> - <ul class="related"> - <li class="story first lipos-1"><a href="http://www.dailytelegraph.com.au/news/charges-laid-over-blind-rape-case/story-e6freuy9-1225817867010"> Charges laid over blind rape case </a></li> - <li class="story lipos-2"><a href="http://www.dailytelegraph.com.au/news/arsonists-torch-salvation-army-op-shop/story-e6freuy9-1225817874459"> Arsonists torch Salvo's Op Shop </a></li> - <li class="story last lipos-3"><a href="http://www.dailytelegraph.com.au/travel/body-scans-for-australias-gateway-sydney-aiport/story-e6frezhr-1225817873681"> Body scans for Australia's gateway </a></li> - </ul> - </div> <!-- // .ci-content --> - <div class="ci-footer"> - <ul class="more-links tier-1"> - <li > - <a href="http://www.dailytelegraph.com.au">More news at The Daily Telegraph </a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .ci-footer --> - </div> <!-- // .ci .collection --> - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-3 cirpos-6" > - <div class="ci default state state-qld collection text-m-queensland id1225749148354" id="id1225749148354"> - <div class="ci-header"> - <h3 class="heading"><a href="http://www.couriermail.com.au/">Queensland -</a></h3> - <ul class="more-links tier-1"> - <li > - <a href="http://www.couriermail.com.au/">The Courier-Mail</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .ci-header --> - <div class="ci-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225818011237"> - <h4 class="heading"> - <a href="http://www.news.com.au/couriermail/story/0,,26574794-3102,00.html"> - QR security guard assaulted - </a> - </h4> - <a href="http://www.news.com.au/couriermail/story/0,,26574794-3102,00.html" class="thumb-link"><img src="http://resources1.news.com.au/images/2010/01/11/1225818/011053-qr-security-guard-assaulted.jpg" alt="QR security guard assaulted" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - A SECURITY guard has been bitten and punched after disturbing a burglar at a Queensland Rail workshop at Redbank, west of Brisbane. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225818011237 --> - <ul class="related"> - <li class="story first lipos-1"><a href="http://www.news.com.au/couriermail/story/0,,26574740-5017790,00.html"> Human stories in wreck </a></li> - <li class="story lipos-2"><a href="http://www.news.com.au/couriermail/story/0,,26574682-5003402,00.html"> Thief had see-through disguise </a></li> - <li class="story last lipos-3"><a href="http://www.news.com.au/couriermail/story/0,,26574658-5003402,00.html"> BBQ fire destroys house </a></li> - </ul> - </div> <!-- // .ci-content --> - <div class="ci-footer"> - <ul class="more-links tier-1"> - <li > - <a href="http://www.couriermail.com.au">More news at The Courier-Mail </a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .ci-footer --> - </div> <!-- // .ci .collection --> - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-4 cirpos-5" > - <div class="ci default state state-vic collection text-m-victoria id1225749150510" id="id1225749150510"> - <div class="ci-header"> - <h3 class="heading"><a href="http://www.heraldsun.com.au/">Victoria</a></h3> - <ul class="more-links tier-1"> - <li > - <a href="http://www.heraldsun.com.au/news/victoria">Victoria</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .ci-header --> - <div class="ci-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817899003"> - <h4 class="heading"> - <a href="http://www.heraldsun.com.au/news/naughty-corners-are-a-bad-idea-for-kids/story-e6frf7jo-1225817899003"> - Naughty corners 'bad for kids' - </a> - </h4> - <a href="http://www.heraldsun.com.au/news/naughty-corners-are-a-bad-idea-for-kids/story-e6frf7jo-1225817899003" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225817/900476-an-expert-has-warned-against-naughty-corners.jpg" alt="An expert has warned against naughty corners" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - A MELBOURNE&nbsp;expert says naughty corners in bedrooms are inappropriate because they shame and humiliate. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225817899003 --> - <ul class="related"> - <li class="story first lipos-1"><a href="http://www.heraldsun.com.au/news/doctors-demand-free-aircon-to-save-elderly/story-e6frf7jo-1225817883985"> Doctors demand free aircon </a></li> - <li class="story lipos-2"><a href="http://www.heraldsun.com.au/news/tight-knit-town-mourns-a-top-bloke/story-e6frf7jo-1225817883046"> Tolmie mourns a top bloke </a></li> - <li class="story last lipos-3"><a href="http://www.heraldsun.com.au/news/are-victorians-racist/story-e6frf7jo-1225817863179"> Are Victorians racist? </a></li> - </ul> - </div> <!-- // .ci-content --> - <div class="ci-footer"> - <ul class="more-links tier-1"> - <li > - <a href="http://www.heraldsun.com.au">More news at Herald Sun </a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .ci-footer --> - </div> <!-- // .ci .collection --> - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-5 cirpos-4" > - <div class="ci default state state-sa collection text-m-south-australia id1225749150536" id="id1225749150536"> - <div class="ci-header"> - <h3 class="heading"><a href="http://www.adelaidenow.com.au/">South Australia</a></h3> - <ul class="more-links tier-1"> - <li > - <a href="http://www.adelaidenow.com.au/">AdelaideNow</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .ci-header --> - <div class="ci-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225818052131"> - <h4 class="heading"> - <a href="http://www.news.com.au/adelaidenow/story/0,,26574924-2682,00.html"> - Craypot crackpots in hoax emergency - </a> - </h4> - <a href="http://www.news.com.au/adelaidenow/story/0,,26574924-2682,00.html" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225818/048752-binoculars.jpg" alt="binoculars" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - PRANKSTERS have tied up already-stretched beach rescue resources with a fake emergency at Middleton. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225818052131 --> - <ul class="related"> - <li class="story first lipos-1"><a href="http://www.news.com.au/adelaidenow/story/0,,26572475-2682,00.html"> Homes saved from fire </a></li> - <li class="story lipos-2"><a href="http://www.news.com.au/adelaidenow/story/0,,26574892-2682,00.html"> Drunken, speeding driver pleads guilty </a></li> - <li class="story last lipos-3"><a href="http://www.news.com.au/adelaidenow/story/0,,26574808-2682,00.html"> Fire fight at RAAF base </a></li> - </ul> - </div> <!-- // .ci-content --> - <div class="ci-footer"> - <ul class="more-links tier-1"> - <li > - <a href="http://www.adelaidenow.com.au">More news at AdelaideNow</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .ci-footer --> - </div> <!-- // .ci .collection --> - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-6 cirpos-3" > - <div class="ci default state state-wa collection text-m-western-australia id1225749150551" id="id1225749150551"> - <div class="ci-header"> - <h3 class="heading"><a href="http://www.perthnow.com.au/">Western Australia</a></h3> - <ul class="more-links tier-1"> - <li > - <a href="">Western Australia</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .ci-header --> - <div class="ci-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817988782"> - <h4 class="heading"> - <a href="http://www.perthnow.com.au/news/western-australia/perth-bouncer-glassed-in-head-at-tiger-lils/story-e6frg13u-1225817988782"> - Perth bouncer glassed - </a> - </h4> - <a href="http://www.perthnow.com.au/news/western-australia/perth-bouncer-glassed-in-head-at-tiger-lils/story-e6frg13u-1225817988782" class="thumb-link"><img src="http://resources3.news.com.au/images/2009/12/21/1225812/336727-glassing-in-sydney.gif" alt="glassing in sydney" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - POLICE have charged a 26-year-old man after a Tiger Lil's Night Club bouncer was glassed in the head in the early hours of Sunday morning. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225817988782 --> - <ul class="related"> - <li class="story first lipos-1"><a href="http://www.perthnow.com.au/news/western-australia/mini-tornado-hits-regional-town/story-e6frg13u-1225817879536"> 'Mini-tornado' hits regional town </a></li> - <li class="story lipos-2"><a href="http://www.perthnow.com.au/news/western-australia/mandurah-trains-stopped-over-weekends/story-e6frg13u-1225817981993"> Mandurah trains weekend closure </a></li> - <li class="story last lipos-3"><a href="http://www.perthnow.com.au/news/western-australia/killer-sharks-to-be-shot-slaughtered/story-e6frg13u-1225817663520"> Rogue sharks to be shot </a></li> - </ul> - </div> <!-- // .ci-content --> - <div class="ci-footer"> - <ul class="more-links tier-1"> - <li > - <a href="http://www.perthnow.com.au">More news at PerthNow</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .ci-footer --> - </div> <!-- // .ci .collection --> - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-7 cirpos-2" > - <!-- Generated at Mon Jan 11 12:49:18 EST 2010 --> - <div class="ci-header"> - <h3 class="heading"> - <a href="http://www.ntnews.com.au/">Northern Territory</a> - </h3> - <p class="rss"> - <a href="http://www.ntnews.com.au/rss/ntnews_ndm.xml">RSS</a> - </p> - <p class="more-link"> - <a href="http://www.ntnews.com.au/">NT News</a> - </p> - </div> <!-- // .ci-header --> - <div class="ci-content"> - <div class="story-block"> - <h3 class="heading"> - <a href="http://www.ntnews.com.au/article/2010/01/11/114561_ntnews.html">Fears for missing pair as floods rise</a> - </h3> - <p>GRAVE fears are held for two brothers who are still missing after flood waters washed through Central Australia.</p> - </div> - <ul class="related"> - <li> - <a href="http://www.ntnews.com.au/article/2010/01/11/114581_ntnews.html">Call for croc trap spy cams</a> - </li> - <li> - <a href="http://www.ntnews.com.au/article/2010/01/11/114571_ntnews.html">Couple lucky to be alive after rollover</a> - </li> - <li> - <a href="http://www.ntnews.com.au/article/2010/01/11/114541_ntnews.html">Marina hit by car break-in crime wave</a> - </li> - <li> - <a href="http://www.ntnews.com.au/article/2010/01/11/114551_ntnews.html">Lucky cross diving deep with meaning</a> - </li> - </ul> - </div><!-- // .ci-content --> - <div class="ci-footer"> - <p class="more-link"> - <a href="http://www.ntnews.com.au/">More news at NT News</a> - </p> - </div><!-- // .ci-footer --> - - - - </div><!-- // .content-item .js-tab-content --> - <div class="content-item tab-content js-tab-content cipos-8 cirpos-1" > - <!-- Generated at Mon Jan 11 12:49:18 EST 2010 --> - <div class="ci-header"> - <h3 class="heading"> - <a href="http://www.themercury.com.au/news/tasmania-news.html">Tasmania</a> - </h3> - <p class="rss"> - <a href="http://www.themercury.com.au/rss/tasmania-news.xml">RSS</a> - </p> - <p class="more-link"> - <a href="http://www.themercury.com.au/">Mercury</a> - </p> - </div> <!-- // .ci-header --> - <div class="ci-content"> - <div class="story-block"> - <h3 class="heading"> - <a href="http://www.themercury.com.au/article/2010/01/11/120761_tasmania-news.html">Bypass deadline warning</a> - </h3> - <p>PREMIER David Bartlett has warned Aboriginal activists opposing the Brighton bypass they may have only two weeks to co-operate before the project proceeds.</p> - </div> - <ul class="related"> - <li> - <a href="http://www.themercury.com.au/article/2010/01/11/120771_tasmania-news.html">Film backs Ady Gil claims</a> - </li> - <li> - <a href="http://www.themercury.com.au/article/2010/01/11/120781_tasmania-news.html">Total fire ban as 36C expected</a> - </li> - <li> - <a href="http://www.themercury.com.au/article/2010/01/11/120801_tasmania-news.html">Molik Monday has Hobart in its grip</a> - </li> - <li> - <a href="http://www.themercury.com.au/article/2010/01/11/120791_tasmania-news.html">Pokie losses keep rising</a> - </li> - </ul> - </div><!-- // .ci-content --> - <div class="ci-footer"> - <p class="more-link"> - <a href="http://www.themercury.com.au/">More news at Mercury</a> - </p> - </div><!-- // .ci-footer --> - - - - </div><!-- // .content-item .js-tab-content --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="/national">More national news</a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module .js-tabbed --> - - </div><!-- // .item ipos-1 irpos-1 --> - </div><!-- // .group-content.item-count-1 --> -</div><!-- // .group --> - - <!-- [Group:1225745348177] on [fwprodcontent02.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group group-accordion text-g-world item-count-1"> - <div class="group-header"> - <h2 class="heading">World</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-1"> - <div class="module default collection mpos-1 mrpos-3 text-m-world id1225745360551" id="id1225745360551"> - <div class="module-header"> - <h3 class="heading"><a href="/world">World</a></h3> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225818014345"> - <h4 class="heading"> - <a href="/world/nightclub-man-wanted-in-playboy-model-murder-case/story-e6frfkyi-1225818014345"> - Dead Playboy model 'left club with man' - </a> - </h4> - <a href="/world/nightclub-man-wanted-in-playboy-model-murder-case/story-e6frfkyi-1225818014345" class="thumb-link"><img src="http://resources1.news.com.au/images/2010/01/07/1225817/046433-dtthumb-paula-sladewski.jpg" alt="Paula Sladewski" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - POLICE are searching for a man who left a club with Paula Sladewski&nbsp; before her body was found burned beyond recognition. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225818014345 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/world/uae-presidents-brother-avoids-torture-charge/story-e6frfkyi-1225817939593"> President's brother avoids torture charge </a></li> - <li class="story lipos-2"><a href="/world/ied-claims-life-of-sunday-mirror-journalist/story-e6frfkyi-1225817939621"> Journalist killed on Afghanistan patrol </a></li> - <li class="story lipos-3"><a href="/world/gunman-who-shot-former-pope-john-paul-ii-seeks-5-million-in-book-deals/story-e6frfkyi-1225817941994"> Gunman seeks $5 million in book deals </a></li> - <li class="story last lipos-4"><a href="/world/workers-tombs-discovered-at-cheops-pyramid/story-e6frfkyi-1225817939813"> Tombs 'show pyramid builders weren't slaves' </a></li> - </ul> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - <div class="module breakingnews breaking-news sectionref-world-breaking-news collection mpos-2 mrpos-2 text-m-world-breaking-news id1225749037374" id="id1225749037374"> - <div class="module-header"> - <h3 class="heading"><a href="/world">World breaking news</a></h3> - <ul class="more-links mpos-2 mrpos-2 tier-1"> - <li > - <a href="/breaking-news/world">World breaking news </a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .module-header --> - <div class="module-content "> - <!-- Placement generated for [1225749037374] on [fwprodcontent02.ni.news.com.au] @ [Mon Jan 11 12:49:16 EST 2010] with unknown deps --> - <ul class="breaking-news-list" > - <li class="lipos-1 lirpos-4"> - <span class="timestamp">12:05PM</span> - <a href="/breaking-news/mp3-creator-backs-perfect-streams-focus-on-dumb-devices/story-e6frfku0-1225818037028">MP3 creator focuses on &#039;dumb&#039; devices</a> - </li> - <li class="lipos-2 lirpos-3"> - <span class="timestamp">11:30AM</span> - <a href="/breaking-news/more-than-210-prisoners-moved-for-mel-gibson-film/story-e6frfku0-1225818020780">Prisoners booted out for Mel Gibson film</a> - </li> - <li class="lipos-3 lirpos-2"> - <span class="timestamp">11:04AM</span> - <a href="/breaking-news/nightclub-man-wanted-in-playboy-model-murder-case/story-e6frfku0-1225818007768">Dead Playboy model &#039;left club with man&#039;</a> - </li> - <li class="lipos-4 lirpos-1"> - <span class="timestamp">8:27AM</span> - <a href="/breaking-news/lindsay-lohan-wanted-after-paparazzi-hit-by-car/story-e6frfku0-1225817943168">Lohan wanted after paparazzi hit by car</a> - </li> - </ul> <!-- // .breaking-news-list --> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - <!-- promo-block-03 --> -<div class="module module-promo-block-03 mpos-3 mrpos-1 id1225818041731 text-m-ice-ice-baby"> - <div class="module-content"> -<!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/world/saturn-energy-could-be-signs-of-life/story-e6frfkyi-1225818036227"><img src="http://resources1.news.com.au/images/2010/01/11/1225818/042521-enceladus.jpg" alt="Enceladus" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/world/saturn-energy-could-be-signs-of-life/story-e6frfkyi-1225818036227">Ice, ice, baby</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Saturn's&nbsp;moon Enceladus could potentially have the right conditions to support life</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="/world">More world </a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module.module-promo-block-03 --> - - </div><!-- // .item ipos-1 irpos-1 --> - </div><!-- // .group-content.item-count-1 --> -</div><!-- // .group --> - - <!-- [Group:1225745348274] on [fwprodcontent04.ni.news.com.au] @ [January 11, 2010 12:48PM] --> -<div class="group group-accordion text-g-fox-sports item-count-1"> - <div class="group-header"> - <h2 class="heading"><a href="http://www.foxsports.com.au">Fox Sports</a></h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-1"> - <div class="module foxsports"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.foxsports.com.au/">Sports News</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> - <div class="story-block "> - <h4 class="heading"><a href="http://www.foxsports.com.au/story/0,8659,26574694-5018866,00.html?from=public_rss">Rebels join race for Thurston</a></h4> - <a href="http://www.foxsports.com.au/story/0,8659,26574694-5018866,00.html?from=public_rss" class="thumb-link"><img class="thumbnail" src="http://www.foxsports.com.au/common/imagedata/0,10114,7413409,00.jpg" alt="Rebels join race for Thurston"/></a> - <p class="standfirst">THE tug of war for Johnathan Thurston's signature has taken a twist, with the Melbourne Super Rugby franchise targeting the star.</p> - </div> - </div><!-- // .module-content --> - <div class="module-related"> - <ul class="related"> - <li class="first lipos-1"><a href="http://www.foxsports.com.au/story/0,8659,26574736-23215,00.html?from=public_rss">Mali conjure miracle in Louanda</a></li> - <li class="lipos-2"><a href="http://www.foxsports.com.au/story/0,8659,26572745-5000940,00.html?from=public_rss">Ten-man Jets overpower Victory</a></li> - <li class="lipos-3"><a href="http://www.foxsports.com.au/story/0,8659,26574359-5018870,00.html?from=public_rss">North to get another chance</a></li> - <li class="last lipos-4"><a href="http://www.foxsports.com.au/story/0,8659,26573885-5018898,00.html?from=public_rss">Hewitt's career hinges on fitness</a></li> - </ul> - </div><!-- // .module-related --> -</div><!-- // .module --> - - <div class="custom-html "> - <div class="module fox-sports"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.foxsports.com.au/#vpl=1">Fox Sports in a minute</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> - <a href="http://www.foxsports.com.au/#vpl=1" class="thumbnail-link"> - <span class="play">Play Video</span> - <img width="189" height="106" src="http://media.foxsports.com.au/fsniam/FSWS_HOURLY_189.jpg" class="thumbnail" alt="FOXSPORTS Video Player"/> - </a> - <p><strong>It's on all the time...</strong> Updates throughout the day</p> - <h5>Available on FoxTel</h5> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links"> - <li class="first"><a href="http://www.foxsports.com.au/results">Live Scores and Stats Central</a></li> - <li class="last"><a href="http://www.foxsports.com.au">More sport</a></li> - </ul> - </div><!-- // .module-footer --> -</div><!-- // .module .fox-sports --> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-1 irpos-1 --> - </div><!-- // .group-content.item-count-1 --> -</div><!-- // .group --> - - <!-- [Group:1225745348429] on [fwprodcontent02.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group group-accordion text-g-business item-count-3"> - <div class="group-header"> - <h2 class="heading">Business</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-3"> - <div class="module default collection mpos-1 mrpos-3 text-m-business id1225745365292" id="id1225745365292"> - <div class="module-header"> - <h3 class="heading">Business</h3> - <ul class="more-links mpos-1 mrpos-3 tier-1"> - <li > - <a href="/business">Business</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225818029523"> - <h4 class="heading"> - <a href="/business/surge-in-job-ads-as-economy-improves/story-e6frfm1i-1225818029523"> - Big jump in number of job ads - </a> - </h4> - <a href="/business/surge-in-job-ads-as-economy-improves/story-e6frfm1i-1225818029523" class="thumb-link"><img src="http://resources1.news.com.au/images/2009/12/10/1225808/990097-jobs.jpg" alt="jobs" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - THE number of job ads in newspapers and online jumped 6 per cent, the strongest monthly growth in two-and-a-half years. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225818029523 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/business/rags-to-riches-tale-as-dotcom-geek-snaps-up-top-harbour-properties/story-e6frfm1i-1225817935527"> Dotcom geek grabs Harbour properties </a></li> - <li class="story lipos-2"><a href="/business/industry-warned-of-labour-pains/story-e6frfm1i-1225817933467"> Industry warned of labour pains </a></li> - <li class="story lipos-3"><a href="/business/australian-share-market-off-and-running/story-e6frfm1i-1225817945839"> Share market off and running </a></li> - <li class="story last lipos-4"><a href="/business/camper-hire-owner-rejects-wicked-criticism/story-e6frfm1i-1225817879213"> Camper hire owner rejects criticism </a></li> - </ul> - </div> <!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links mpos-1 mrpos-3 tier-1"> - <li > - <a href="/business">More business</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .module-footer --> - </div> <!-- // .module .collection --> - - <!-- Generated at Mon Jan 11 12:49:05 EST 2010 --> - <div class="module asx-top-risers-fallers mpos-2 mrpos-2 id1225748188344"> - <div class="module-header"> - <h3 class="heading">ASX200 - Top Gainers &amp; Losers - <em class="timestamp"> at 12:00PM</em> - </h3> - </div><!-- // .module-header --> - <div class="module-content"> - <table class="market-table"> - <thead> - <tr><th scope="col" class="th-code">Code</th><th scope="col" class="th-name">Name</th><th scope="col" class="th-price">Price</th><th scope="col" class="th-percent movement-index">Percent</th></tr> - </thead> - <tbody class="gain"> - <tr > - <td class="asx-code"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=NXS">NXS</a> - </td> - <td class="company"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=NXS">Nexus Energy</a> - </td> - <td class="price">0.335</td> - <td class="movement"><span>8.06%</span></td> - </tr> - <tr > - <td class="asx-code"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=AGO">AGO</a> - </td> - <td class="company"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=AGO">Atlas Iron</a> - </td> - <td class="price">2.28</td> - <td class="movement"><span>6.04%</span></td> - </tr> - <tr class="last"> - <td class="asx-code"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=PLA">PLA</a> - </td> - <td class="company"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=PLA">Platinum Austra..</a> - </td> - <td class="price">1.13</td> - <td class="movement"><span>5.6%</span></td> - </tr> - </tbody> - <tbody class="loss"> - <tr class="last"> - <td class="asx-code"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=SDL">SDL</a> - </td> - <td class="company"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=SDL">Sundance Resour..</a> - </td> - <td class="price">0.155</td> - <td class="movement"><span>-3.13%</span></td> - </tr> - <tr class="last"> - <td class="asx-code"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=WOR">WOR</a> - </td> - <td class="company"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=WOR">WorleyParsons L..</a> - </td> - <td class="price">29.61</td> - <td class="movement"><span>-2.8%</span></td> - </tr> - <tr class="last"> - <td class="asx-code"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=PBG">PBG</a> - </td> - <td class="company"> - <a href="http://markets.news.com.au/newscorp/entry.aspx?secid=PBG">Pacific Brands</a> - </td> - <td class="price">1.165</td> - <td class="movement"><span>-2.52%</span></td> - </tr> - </tbody> - </table> - </div> <!-- // .module-content --> - <div class="module-footer"> - <p class="more-link"><a href="##share-market ">More Gainers &amp; Losers</a></p> - </div><!-- // .module-footer --> - </div><!-- // .module .asx-top-risers-fallers --> - - - <div class="module stock-quotes id1225709921880 "> - <div class="module-header"> - <h3 class="heading">Stock Quotes</h3> - </div><!-- // . module-header --> - <div class="module-content"> - <form action="http://markets.news.com.au/newscorp/entry.aspx" method="get"> - <label for="SecId">Search ASX code or name</label> - <input type="text" value="Search ASX code or Name" name="SecId" class="default-input remove-text inactive-input"/> - <input type="submit" value="Search" class="search-submit submit"/> - </form> - </div><!-- // .module-content --> -</div><!-- // .module --> - - </div><!-- // .item ipos-1 irpos-3 --> - <div class="item ipos-2 irpos-2"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Business Accordion 2 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755080235&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Breaking News</a></li> - <li class="last"><a href="">Markets</a></li> -</ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-2 irpos-2 --> - <div class="item ipos-3 irpos-1"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Business Accordion 3</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755080316&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="#">Most Read</a></li> - <li class="last"><a href="#">Business Smarts</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-3 irpos-1 --> - </div><!-- // .group-content.item-count-3 --> -</div><!-- // .group --> - - <!-- [Group:1225745357515] on [fwprodcontent03.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group group-accordion text-g-money item-count-3"> - <div class="group-header"> - <h2 class="heading">Money </h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-3"> - <div class="module default collection mpos-1 mrpos-2 text-m-money id1225745763407" id="id1225745763407"> - <div class="module-header"> - <h3 class="heading">Money</h3> - <ul class="more-links mpos-1 mrpos-2 tier-1"> - <li > - <a href="/money">Money</a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817925833"> - <h4 class="heading"> - <a href="/money/superannuation/add-some-spice-to-your-super-nest-egg/story-e6frfmdi-1225817925833"> - Add some spice to your super nest egg - </a> - </h4> - <a href="/money/superannuation/add-some-spice-to-your-super-nest-egg/story-e6frfmdi-1225817925833" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225817/926312-retirees.jpg" alt="retirees" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - MANY people think super is boring, but there are ways to spice up your nest egg that are definitely not dull. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225817925833 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/money/money-matters/get-credit-where-credits-due/story-e6frfmd9-1225817923298"> Get credit where credit's due </a></li> - <li class="story lipos-2"><a href="/money/can-you-afford-to-get-sick/story-e6frfmci-1225817771388"> Can you afford to get sick? </a></li> - <li class="story lipos-3"><a href="/money/interest-rates/christmas-shopping-spree-may-spell-rate-rise/story-e6frfmn0-1225817199662"> Christmas spree may spell rate rise </a></li> - <li class="story last lipos-4"><a href="/money/money-matters/more-people-cutting-deals-on-debt/story-e6frfmd9-1225817270124"> More people cutting deals on debt </a></li> - </ul> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - <!-- Multi-Promo --> -<div class="module multi-promo ci-count-2 mpos-2 mrpos-1 first-image-152w86h text-m-news-home-multi-promo-money id1225787186529"> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-2"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="http://www.news.com.au/business/money/feature/0,,5016109,00.html"><img src="http://resources3.news.com.au/images/2009/11/17/1225798/578543-stock-business-money.jpg" alt="interest rates dice" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="http://www.news.com.au/business/money/feature/0,,5016109,00.html">Rates race</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Find out how interest rates affect you with the latest news, features and multimedia</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-2 cirpos-1"> - <!-- promo-block-b --> - <div class="promo-block promo-block-06 "> - <div class="promo-image"><a href="http://www.news.com.au/business/money/matters/"><img src="http://resources1.news.com.au/images/2009/11/17/1225798/640189-compass-on-australia-dollar-bill.jpg" alt="Money compass" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="http://www.news.com.au/business/money/matters/">Money and Me</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Join everyday Australians and financial experts and chat about money issues that matter</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-06 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="/money">More money </a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module.multi-promo --> - - </div><!-- // .item ipos-1 irpos-3 --> - <div class="item ipos-2 irpos-2"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Money Accordion 2 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755103685&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Property</a></li> - <li class="last"><a href="">Investing</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-2 irpos-2 --> - <div class="item ipos-3 irpos-1"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Money Accordion 3 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755107345&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Banking</a></li> - <li class="last"><a href="">Superannuation</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-3 irpos-1 --> - </div><!-- // .group-content.item-count-3 --> -</div><!-- // .group --> - - <!-- [Group:1225745357552] on [fwprodcontent02.ni.news.com.au] @ [January 11, 2010 12:48PM] --> -<div class="group group-accordion text-g-entertainment item-count-3"> - <div class="group-header"> - <h2 class="heading">Entertainment</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-3"> - <div class="module default collection mpos-1 mrpos-2 text-m-news-home-plmt-ents-top-stories id1225745766149" id="id1225745766149"> - <div class="module-header"> - <ul class="more-links mpos-1 mrpos-2 tier-1"> - <li > - <a href="/entertainment">Entertainment </a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817930820"> - <h4 class="heading"> - <a href="/entertainment/celebrity/britney-spears-catches-lover-jason-trawick-with-two-women/story-e6frfmqi-1225817930820"> - Spears 'catches lover with two women' - </a> - </h4> - <a href="/entertainment/celebrity/britney-spears-catches-lover-jason-trawick-with-two-women/story-e6frfmqi-1225817930820" class="thumb-link"><img src="http://resources2.news.com.au/images/2010/01/11/1225817/927858-britney-spears.jpg" alt="Britney Spears" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - BRITNEY Spears is single again after she caught her boyfriend with two other women. - </p> <!-- // .standfirst --> - <p class="comments"> - <a href="/entertainment/celebrity/britney-spears-catches-lover-jason-trawick-with-two-women/comments-e6frfmqi-1225817930820">14 comments on this story</a> - </p> - </div> <!-- // .story-block pos-1 rpos-1 id1225817930820 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/entertainment/music/austereos-jackie-o-breaks-her-silence-on-radio-stunt/story-e6frfn09-1225817871923"> Jackie O's so sorry for radio show stunt </a></li> - <li class="story lipos-2"><a href="/entertainment/celebrity/lesbian-tila-tequila-tells-twitter-she-may-be-pregnant-after-losing-casey-johnson/story-e6frfmqi-1225817933182"> Lesbian Tila's Twitter baby rant </a></li> - <li class="story lipos-3"><a href="/entertainment/celebrity/lindsay-lohan-involved-in-hit-and-run-incident-in-los-angeles/story-e6frfmqi-1225817933341"> Lindsay Lohan involved in hit and run </a></li> - <li class="story last lipos-4"><a href="/entertainment/movies/avatar-continues-to-dominate-at-the-box-office/story-e6frfmvr-1225817934520"> Avatar continues to dominate </a></li> - </ul> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - <!-- Multi-Promo --> -<div class="module multi-promo ci-count-2 mpos-2 mrpos-1 first-image-152w86h text-m-entertainment-features id1225745651252"> - <div class="module-header"> - <h3 class="heading">Entertainment Features </h3> - </div><!-- // .module-header --> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-2"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/entertainment/celebrity/dennis-rodman-thrown-out-of-restaurant/story-e6frfmqi-1225817828321"><img src="http://resources1.news.com.au/images/2010/01/10/1225817/830509-dennis-rodman.jpg" alt="Dennis Rodman" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/entertainment/celebrity/dennis-rodman-thrown-out-of-restaurant/story-e6frfmqi-1225817828321">Kicked out</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Dennis Rodman reportedly thrown out of restaurant for being drunk and disorderly</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-2 cirpos-1"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/entertainment/celebrity/gallery-e6frfmr9-1111120736498"><img src="http://resources2.news.com.au/images/2010/01/08/1225817/252610-tila-tequila.jpg" alt="Tila Tequila" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/entertainment/celebrity/gallery-e6frfmr9-1111120736498">Shooting stars</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Tila Tequila argues with two celebs in catfight at her home<br /> -<img alt="" src="http://network.news.com.au/images/i_related.gif" /> <a href="http://www.news.com.au/tila-tequila-bids-casey-johnson-tearful-goodbye-in-bizarre-video-message/story-e6frfmq9-1225817219861">Odd farewell to Casey</a></p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="/entertainment">More entertainment </a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module.multi-promo --> - - </div><!-- // .item ipos-1 irpos-3 --> - <div class="item ipos-2 irpos-2"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Entertainment Accordion 2 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755115280&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Television</a></li> - <li class="last"><a href="">Movies</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-2 irpos-2 --> - <div class="item ipos-3 irpos-1"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Entertainment Accordion 3 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755115346&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Music</a></li> - <li class="last"><a href="">Fashion</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-3 irpos-1 --> - </div><!-- // .group-content.item-count-3 --> -</div><!-- // .group --> - - <!-- [Group:1225745357890] on [fwprodcontent04.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group group-accordion text-g-travel item-count-3"> - <div class="group-header"> - <h2 class="heading">Travel</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-3"> - <div class="module default sectionref-send-to-news-home collection mpos-1 mrpos-2 text-m-news-home-plmt-trav-top-stories id1225745793844" id="id1225745793844"> - <div class="module-header"> - <ul class="more-links mpos-1 mrpos-2 tier-1"> - <li > - <a href="/travel">Travel </a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817364839"> - <h4 class="heading"> - <a href="/travel/travel-advice/emirates-a380-first-class-luxury/story-e6frfqfr-1225817364839"> - Is this the most desired airline seat? - </a> - </h4> - <a href="/travel/travel-advice/emirates-a380-first-class-luxury/story-e6frfqfr-1225817364839" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/08/1225817/377992-news-image-a380-20100108.jpg" alt="A380" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - DOM Perignon, a private mini-bar and a shower suit - Brian Crisp enters a world of unimaginable luxury on the Emirates A380. - </p> <!-- // .standfirst --> - <ul class="related"> - <li class="gallery first last lipos-1"><a href="/travel/galleries/gallery-e6frflw0-1111120331039"><strong class="kicker">Onboard:</strong> Emirate&#039;s luxury A380 </a></li> - </ul> - </div> <!-- // .story-block pos-1 rpos-1 id1225817364839 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/travel/news/tourists-label-wicked-campervans-death-traps/story-e6frfq80-1225817940969"> Tourists label campervans 'death traps' </a></li> - <li class="story lipos-2"><a href="/travel/news/tourists-burnt-in-hong-kong-acid-attack/story-e6frfq80-1225817945504"> Tourists burnt in acid attack </a></li> - <li class="story lipos-3"><a href="/travel/news/the-white-cockatoo-resort-up-for-sale/story-e6frfq80-1225817882094"> Sex resort starts new life with a bang </a></li> - <li class="story last lipos-4"><a href="/travel/news/tourists-get-green-card-to-keep-climbing-uluru/story-e6frfq80-1225817368971"> Government rejects Uluru climbing ban </a></li> - </ul> - </div> <!-- // .module-content --> - </div> <!-- // .module .collection --> - - <!-- Multi-Promo --> -<div class="module multi-promo ci-count-2 mpos-2 mrpos-1 first-image-152w86h text-m-travel-features id1225745653012"> - <div class="module-header"> - <h3 class="heading">Travel Features</h3> - </div><!-- // .module-header --> - <div class="module-content"> - <div class="content-item cipos-1 cirpos-2"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/travel/news/drag-queen-flight-uniforms-fail-to-take-off/story-e6frfq80-1225817820001"><img src="http://resources3.news.com.au/images/2010/01/10/1225817/833295-pink-dress.jpg" alt="Pink dress" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/travel/news/drag-queen-flight-uniforms-fail-to-take-off/story-e6frfq80-1225817820001">Hostie uniform anger</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Airline under fire for its new uniforms which have been likened to drag queen's outfits</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - <div class="content-item cipos-2 cirpos-1"> - <!-- promo-block-b --> - <div class="promo-block promo-block-03 "> - <div class="promo-image"><a href="/travel/world/central-park-by-bike/story-e6frfqc9-1225817404927"><img src="http://resources2.news.com.au/images/2010/01/11/1225817/942294-news-image-bike-20100111.jpg" alt="Bike" width="152" height="86" /></a></div><!-- // .promo-image --> - <div class="promo-inner"> - <div class="promo-heading"><h4 class="heading"><a href="/travel/world/central-park-by-bike/story-e6frfqc9-1225817404927">NY by bike</a></h4></div><!-- // .promo-heading --> - <div class="promo-text"><p>Explore one of the world's most famous parks in style. But watch out for strollers</p></div><!-- // .promo-text --> - </div><!-- // .promo-inner --> - </div><!-- // .promo-block.promo-block-03 --> - - </div><!-- // .content-item --> - </div><!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links tier-1"> - <li > - <a href="/travel">More travel</a> - </li> - </ul> <!-- // .tier-1 --> - - </div><!-- // .module-footer --> -</div><!-- // .module.multi-promo --> - - </div><!-- // .item ipos-1 irpos-3 --> - <div class="item ipos-2 irpos-2"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Travel Accordion 2 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755121873&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Australia</a></li> - <li class="last"><a href="">World Destinations</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-2 irpos-2 --> - <div class="item ipos-3 irpos-1"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Travel Accordion 3 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755125095&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Holiday Ideas</a></li> - <li class="last"><a href="">Travel Advice</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-3 irpos-1 --> - </div><!-- // .group-content.item-count-3 --> -</div><!-- // .group --> - - <!-- [Group:1225745358988] on [fwprodcontent02.ni.news.com.au] @ [January 11, 2010 12:49PM] --> -<div class="group group-accordion text-g-technology item-count-2"> - <div class="group-header"> - <h2 class="heading">Technology </h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-2"> - <div class="module default collection mpos-1 mrpos-3 text-m-news-home-plmt-tech-top-stories id1225746283841" id="id1225746283841"> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817940987"> - <h4 class="heading"> - <a href="/technology/laser-projector-boxee-web-tv-box-win-ces-gadget-awards/story-e6frfro0-1225817940987"> - Pocket projector, web TV box win awards - </a> - </h4> - <a href="/technology/laser-projector-boxee-web-tv-box-win-ces-gadget-awards/story-e6frfro0-1225817940987" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225817/941032-consumer-electronics-show.jpg" alt="Consumer Electronics Show" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - <strong class="standfirst-kicker "></strong> - A TINY laser projector and a set-top box which delivers web content to your TV among the best products at gadget show CES. - </p> <!-- // .standfirst --> - <p class="comments"> - <a href="/technology/laser-projector-boxee-web-tv-box-win-ces-gadget-awards/comments-e6frfro0-1225817940987">4 comments on this story</a> - </p> - </div> <!-- // .story-block pos-1 rpos-1 id1225817940987 --> - <ul class="related"> - <li class="story first lipos-1"><a href="/technology/worlds-first-life-size-robotic-girlfriend/story-e6frfro0-1225817805297"> World's first life-size robotic girlfriend </a></li> - <li class="story lipos-2"><a href="/technology/e-reader-makers-seek-bestseller-but-may-be-has-beens-after-ces/story-e6frfro0-1225817945583"> E-readers could be has-beens after CES </a></li> - <li class="story lipos-3"><a href="/technology/apple-tablet-supply-chain-points-to-q2-launch-sources/story-e6frfro0-1225817943041"> Apple tablet 'due in second quarter' </a></li> - <li class="story last lipos-4"><a href="/technology/mobile-phone-calls-safe-from-hackers/story-e6frfro0-1225817249286"> Mobile calls 'safe from eavesdroppers' </a></li> - </ul> - </div> <!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links mpos-1 mrpos-3 tier-1"> - <li > - <a href="/technology">More technology</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .module-footer --> - </div> <!-- // .module .collection --> - - <div class="module default collection mpos-2 mrpos-2 text-m-industry-news-from-australianit id1225765622100" id="id1225765622100"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.australianit.news.com.au/">Industry news from <em>AustralianIT</em></a></h3> - <ul class="more-links mpos-2 mrpos-2 tier-1"> - <li class="nav-australian-it "> - <a class="nav-australian-it " href="http://www.australianit.news.com.au/">AUSTRALIAN IT </a> - </li> - </ul> <!-- // .tier-1 --> - </div> - <!-- // .module-header --> - <div class="module-content "> - <div class="story-block sbpos-1 sbrpos-1 id1225817871516"> - <h4 class="heading"> - <a href="http://www.theaustralian.com.au/business/second-leg-of-digital-auction-ramped-up/story-e6frg8zx-1225817871516"> - Digital auction ramped up - </a> - </h4> - <a href="http://www.theaustralian.com.au/business/second-leg-of-digital-auction-ramped-up/story-e6frg8zx-1225817871516" class="thumb-link"><img src="http://resources0.news.com.au/images/2010/01/11/1225817/938096-stephen-conroy.jpg" alt="Stephen Conroy" class="thumbnail" width="100" height="75" /></a> - <p class="standfirst"> - &#65279;A SOARING demand for limited bandwidth is putting the heat on key players ahead of the second leg of the government's digital spectrum auction. - </p> <!-- // .standfirst --> - </div> <!-- // .story-block pos-1 rpos-1 id1225817871516 --> - <ul class="related"> - <li class="story first lipos-1"><a href="http://www.theaustralian.com.au/australian-it/rta-facial-recognition-to-catch-driving-licence-cheats/story-e6frgakx-1225817961905"> Facial recognition to catch licence cheats </a></li> - <li class="story lipos-2"><a href="http://www.theaustralian.com.au/australian-it/qld-ticketing-theft-case-investigated/story-e6frgakx-1225817967045"> Qld ticketing 'theft' investigated </a></li> - <li class="story lipos-3"><a href="http://www.theaustralian.com.au/australian-it/google-planning-nexus-enterprise-phone/story-e6frgakx-1225818029070"> Google planning Nexus enterprise phone </a></li> - <li class="story last lipos-4"><a href="http://www.theaustralian.com.au/australian-it/apple-tablet-supply-chain-points-to-q2-launch/story-e6frgakx-1225818027053"> Apple tablet supply chain points to Q2 launch </a></li> - </ul> - </div> <!-- // .module-content --> - <div class="module-footer"> - <ul class="more-links mpos-2 mrpos-2 tier-1"> - <li > - <a href="http://www.australianit.com.au/">Australian IT</a> - </li> - </ul> <!-- // .tier-1 --> - </div> <!-- // .module-footer --> - </div> <!-- // .module .collection --> - - <!-- promo-image-01 --> -<div class="module module-promo-image-01 mpos-3 mrpos-1 id1225807041184 text-m-news-home-tech-promo-mini-rec-laptop-guide"> - <div class="module-content"> -<!-- promo-image-a --> -<div class="promo-block promo-image-01 "> - <div class="promo-image"><a href="/technology/guides/buying-a-laptop-beginners-guide/story-e6frfrui-1111118744566"><img src="http://resources0.news.com.au/images/2009/12/04/1225807/051964-news-home-intel-buyers-guide.GIF" alt="news home intel buyers guide" width="300" height="30" /></a></div><!-- // .promo-image --> -</div><!-- // .promo-block.promo-image-01 --> - </div><!-- // .module-content --> -</div><!-- // .module.module-promo-image-01 --> - - </div><!-- // .item ipos-1 irpos-2 --> - <div class="item ipos-2 irpos-1"> - <div class="custom-html "> - <div class="module ajaxcontent"> - <div class="module-header"> - <h3 class="heading"> - <a href="">Technology Accordion 2 test</a> - </h3> - <ul class="more-links"> - <li class="first"><a rel="/cs/Satellite?c=News_Group&cid=1225755125390&pagename=Foundation%2FNews_Group%2FFDNdetail&site=NewsComAu" href="">Reviews</a></li> - <li class="last"><a href="">Technology Features</a></li> - </ul> - </div> -</div> - </div><!-- // .custom-html --> - - </div><!-- // .item ipos-2 irpos-1 --> - </div><!-- // .group-content.item-count-2 --> -</div><!-- // .group --> - - </div><!-- // #content-4 --> - <div id="content-5"> - <!-- Generated at Mon Jan 11 12:44:09 EST 2010 --> - <div class="module network-most-popular tabbed js-tabbed "> - <div class="module-header"> - <h3 class="heading">Today's Most Popular</h3> - </div><!-- // .module-header --> - <div class="controls"> - <ul class="tab-set"> - <li class="tab js-tab lipos-1 lirpos-2"><a href="#most-popular-articles"><span>Most Popular</span> Articles</a></li> - <li class="tab js-tab lipos-2 lirpos-1"><a href="#most-popular-blogs"><span>Most Popular</span> Blogs</a></li> - </ul><!-- // .tab-set --> - </div><!-- // .controls --> - <div class="module-content"> - <div class="content-item most-popular-articles tab-content js-tab-content cipos-1 cirpos-2" id="most-popular-articles"> - <div class="ci-header"><h4 class="heading">Today's Most Popular Articles</h4></div><!-- // .ci-header --> - <div class="most-pop-item most-pop-major most-pop-news-com-au"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.news.com.au/">News.com.au</a></h5></div> - <ol> - <li><a href="http://www.news.com.au/story/0,1,26573121-23109,00.html">'Crime lord's' penis falls off in raid</a></li> - <li><a href="http://www.news.com.au/story/0,1,26574510-401,00.html">Bali 'confession' to help mate</a></li> - <li><a href="http://www.news.com.au/story/0,1,26574277-421,00.html">Teens mutilated by botched circumcisions </a></li> - <li><a href="http://www.news.com.au/travel/story/0,1,26573967-5014090,00.html">Sex resort starts new life with a bang </a></li> - <li><a href="http://www.news.com.au/entertainment/story/0,1,26573657-7484,00.html">Jackie O's so sorry for radio show stunt </a></li> - <li><a href="http://www.news.com.au/entertainment/story/0,1,26574603-5013560,00.html">Spears 'catches lover with two women'</a></li> - <li><a href="http://www.news.com.au/entertainment/story/0,1,26574711-5013560,00.html">Ricki-Lee says women don't relate to Jen</a></li> - <li><a href="http://www.news.com.au/business/story/0,1,26574546-462,00.html">Australia's new leading state revealed </a></li> - <li><a href="http://www.news.com.au/technology/story/0,1,26572369-5014239,00.html">World's first life-size robotic girlfriend</a></li> - <li><a href="http://www.news.com.au/business/story/0,1,26573967-462,00.html">Sex resort starts new life with a bang </a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/mostpopular">View more most popular</a></p> - </div><!-- // .most-pop-item --> - <div class="most-pop-item most-pop-standard most-pop-adelaide-now"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.news.com.au/adelaidenow/">Adelaide Now</a></h5></div> - <ol> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/adelaidenow/story/0,1,26573363-5006301,00.html">Copycat hijacks Rann's identity</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/adelaidenow/story/0,1,26572475-5006301,00.html">Homes saved from fire</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/adelaidenow/story/0,1,26573465-2682,00.html">Leaner public service urged</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/adelaidenow/story/0,1,26570420-5006301,00.html">Rann caught out on water</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/adelaidenow/story/0,1,26572914-5006301,00.html">Low rent on the way out</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/adelaidenow/">View Adelaide Now</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-adelaide-now --> - <div class="most-pop-item most-pop-standard most-pop-the-australian"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.theaustralian.news.com.au">The Australian</a></h5></div> - <ol> - <li><a rel="track-mostpopfooter" href="http://www.theaustralian.com.au/story/0,1,26574071-2702,00.html">Japan pins whale row on Gillard</a></li> - <li><a rel="track-mostpopfooter" href="http://www.theaustralian.com.au/story/0,1,26573620-2702,00.html">Indian's injuries raise questions</a></li> - <li><a rel="track-mostpopfooter" href="http://www.theaustralian.com.au/story/0,1,26574042-2702,00.html">Brothers swept away in Red Centre flood</a></li> - <li><a rel="track-mostpopfooter" href="http://www.theaustralian.com.au/story/0,1,26573994-2702,00.html">Graduate skills shortage looming</a></li> - <li><a rel="track-mostpopfooter" href="http://www.theaustralian.com.au/business/story/0,1,26573288-643,00.html">Exports to China fall as markets rally</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.theaustralian.news.com.au">View The Australian</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-the-australian --> - <div class="most-pop-item most-pop-standard most-pop-the-daily-telegraph"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.news.com.au/dailytelegraph/">Daily Telegraph</a></h5></div> - <ol> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/dailytelegraph/story/0,1,26572995-5006002,00.html">Jackie O ready to walk</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/dailytelegraph/story/0,1,26574609-5001021,00.html">Superstud Tiger's sex, cash Xmas romp</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/dailytelegraph/story/0,1,26573118-5006002,00.html">Shire boy is in like Flynn</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/dailytelegraph/story/0,1,26572862-5001026,00.html">Twilight star bares all in body paint</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/dailytelegraph/story/0,1,26573154-5012772,00.html">'Crime lord's' penis falls off in raid</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/dailytelegraph/">View Daily Telegraph</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-the-daily-telegraph --> - <div class="most-pop-item most-pop-standard most-pop-perth-now"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.news.com.au/perthnow/">Perth Now</a></h5></div> - <ol> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/perthnow/story/0,1,26571745-5005368,00.html">Sexy Cilmi not so sweet</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/perthnow/story/0,1,26574698-948,00.html">Death row deal to save Rush</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/perthnow/story/0,1,26572320-2761,00.html">Five assaults, fire-bomb in violent night</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/perthnow/story/0,1,26574068-2761,00.html">'Mini-tornado' hits regional town</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/perthnow/story/0,1,26574686-5017320,00.html">Signs of life on Saturn</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/perthnow/">View Perth Now</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-perth-now --> - <div class="most-pop-item most-pop-standard most-pop-the-courier-mail"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.news.com.au/couriermail/">The Courier Mail</a></h5></div> - <ol> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/couriermail/story/0,1,26572647-952,00.html">Swingers resort for sale</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/couriermail/story/0,1,26572585-3102,00.html">Tatts the way they like it</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/couriermail/story/0,1,26574634-952,00.html">Mum 'drove kids at 6 times legal limit'</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/couriermail/story/0,1,26573499-952,00.html">Motorists burned at bowser</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/couriermail/story/0,1,26574615-3102,00.html">Man dies in ute rollover</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/couriermail/">View The Courier Mail</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-the-courier-mail --> - <div class="most-pop-item most-pop-standard most-pop-herald-sun"> - <div class="mpi-header"><h5 class="heading"><a href="http://www.news.com.au/heraldsun/">The Herald Sun</a></h5></div> - <ol> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/heraldsun/story/0,1,26573342-28957,00.html">Jackie O almost quit over stunt</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/heraldsun/story/0,1,26573247-661,00.html">Are Victorians racist?</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/heraldsun/story/0,1,26573285-28957,00.html">Romance off to Rocky start</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/heraldsun/story/0,1,26573835-11088,00.html">The next Warnie</a></li> - <li><a rel="track-mostpopfooter" href="http://www.news.com.au/heraldsun/story/0,1,26574086-661,00.html">Doctors demand free aircon</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/heraldsun/">View The Herald Sun</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-herald-sun --> - </div><!-- // .content-item .most-popular-articles .js-tab-content .js-active-content --> - <div class="content-item most-popular-blogs js-tab-content"> - <div class="most-pop-item most-pop-major most-pop-news-site"> - <h5 class="heading"><a href="http://www.news.com.au">News.com.au</a></h5> - <ol> - <li><a href="http://blogs.news.com.au/bossy/index.php/news/comments/what_should_i_do_about_my_friend_the_home_wrecker">What should I do about my friend the home wrecker?</a></li> - <li><a href="http://blogs.news.com.au/bossy/index.php/news/comments/i_did_a_bad_thing_at_the_office_xmas_party">I did a bad thing at the office xmas party</a></li> - <li><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/the_mmm_monday_mourning_marvel4">The M.M.M (Monday Mourning Marvel)</a></li> - <li><a href="http://blogs.news.com.au/bossy/index.php/news/comments/how_can_i_get_him_to_work_on_his_technique">How can I get him to work on his technique?</a></li> - <li><a href="http://blogs.news.com.au/bossy/index.php/news/comments/i_had_sex_with_my_mates_16_year_old_daughter">I had sex with my friend&#8217;s 16-year-old daughter. What should I do?</a></li> - <li><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/what_is_a_self_potato">What is a self-potato?</a></li> - <li><a href="http://blogs.news.com.au/subpub/index.php/news/comments/once_and_future_prints">Tomorrow&#8217;s news</a></li> - <li><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/the_most_beautiful_women_in_the_world">The most beautiful men and women in the world</a></li> - <li><a href="http://blogs.news.com.au/bossy/index.php/news/comments/has_my_girlfriend_slept_with_too_many_men">Has my girlfriend slept with too many men?</a></li> - <li><a href="http://blogs.news.com.au/news/splat/index.php/news/comments/people_do_like_to_complain_dont_they">People do like to complain, don&#8217;t they?</a></li> - </ol> - <p class="most-pop-more-link"><a href="http://www.news.com.au/">View News.com.au</a></p> - </div><!-- // .most-pop-item .most-pop-major .most-pop-news-site --> - <div class="most-pop-item most-pop-standard most-pop-adelaide-now"> - <h5 class="heading"><a href="http://www.news.com.au/adelaidenow/">Adelaide Now</a></h5> - <p class="most-pop-more-link"><a href="http://www.news.com.au/adelaidenow/">View Adelaide Now</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-adelaide-now --> - <div class="most-pop-item most-pop-standard most-pop-the-australian"> - <h5 class="heading"><a href="http://www.theaustralian.news.com.au">The Australian</a></h5> - <p class="most-pop-more-link"><a href="http://www.theaustralian.news.com.au">View The Australian</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-the-australian --> - <div class="most-pop-item most-pop-standard most-pop-dailytele"> - <h5 class="heading"><a href="http://www.news.com.au/dailytelegraph/">Daily Telegraph</a></h5> - <p class="most-pop-more-link"><a href="http://www.news.com.au/dailytelegraph/">View Daily Telegraph</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-dailytele --> - <div class="most-pop-item most-pop-standard most-pop-perth-now"> - <h5 class="heading"><a href="http://www.news.com.au/perthnow/">PerthNow</a></h5> - <p class="most-pop-more-link"><a href="http://www.news.com.au/perthnow/">View PerthNow</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-perth-now --> - <div class="most-pop-item most-pop-standard most-pop-courier-mail"> - <h5 class="heading"><a href="http://www.news.com.au/couriermail/">The Courier Mail</a></h5> - <p class="most-pop-more-link"><a href="http://www.news.com.au/couriermail/">View The Courier Mail</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-courier-mail --> - <div class="most-pop-item most-pop-standard most-pop-herald-sun"> - <h5 class="heading"><a href="http://www.news.com.au/heraldsun/">The Herald Sun</a></h5> - <p class="most-pop-more-link"><a href="http://www.news.com.au/heraldsun/">View The Herald Sun</a></p> - </div> <!-- // .most-pop-item .most-pop-standard .most-pop-herald-sun --> - </div><!-- // .content-item .most-popular-blogs .js-tab-content --> - </div><!-- // .module-content --> -</div><!-- // .module .network-most-popular --> - - <!-- [Group:1225752681579] on [fwprodcontent05.ni.news.com.au] @ [January 11, 2010 12:40PM] --> -<div class="group text-g-network-sites item-count-1"> - <div class="group-header"> - <h2 class="heading">Network Sites</h2> - </div><!-- // .group-header --> - <div class="group-content"> - <div class="item ipos-1 irpos-1"> - <div class="custom-html "> - <script type="text/javascript"> -if(ndm.load && ndm.load.newshome && ndm.load.newshome.draganddrop) { - ndm.load.newshome.draganddrop(); -} -</script> - </div><!-- // .custom-html --> - - <!-- promo-block-story --> -<div class="module module-promo-block-story mpos-2 mrpos-4 id1225752669197 text-m-the-latest-career-news-amp-advice"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.careerone.com.au/?CMP=BAC-news_ia&amp;attr=tab_resumeadvice">The Latest Career News &amp; Advice</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> -<!-- promo-block-a --> -<div class="story-block"> - <h4 class="heading"> - <a href="http://www.careerone.com.au/">Stay Up To Date</a> - </h4> - <a href="http://www.careerone.com.au/" class="thumb-link"><img src="http://resources0.news.com.au/images/2009/12/02/1225806/076004-woman-at-computer.jpg" alt="woman at computer" class="thumbnail" width="100" height="66" /></a> - <div class="standfirst"><p>Stay up to date with the latest career news and advice with the free Networker Career Newsletter</p></div> -</div><!-- // .story-block --> - </div><!-- // .module-content --> -</div><!-- // .module.module-promo-block-story --> - - <!-- promo-block-story --> -<div class="module module-promo-block-story mpos-3 mrpos-3 id1225752669439 text-m-cars"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.carsguide.com.au">Cars</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> -<!-- promo-block-a --> -<div class="story-block"> - <h4 class="heading"> - <a href="http://www.carsguide.com.au/site/buy-a-car/">Buy a car</a> - </h4> - <a href="http://www.carsguide.com.au/site/buy-a-car/" class="thumb-link"><img src="http://resources2.news.com.au/images/2009/12/14/1225810/288306-buy-a-car.gif" alt="Buy A Car" class="thumbnail" width="100" height="75" /></a> - <div class="standfirst"><p> -<link rel="File-List" href="file:///C:%5CDOCUME%7E1%5Ccolemanj%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml" /><!--[if gte mso 9]><xml> -<w:WordDocument> -<w:View>Normal</w:View> -<w:Zoom>0</w:Zoom> -<w:PunctuationKerning /> -<w:ValidateAgainstSchemas /> -<w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> -<w:IgnoreMixedContent>false</w:IgnoreMixedContent> -<w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> -<w:Compatibility> -<w:BreakWrappedTables /> -<w:SnapToGridInCell /> -<w:WrapTextWithPunct /> -<w:UseAsianBreakRules /> -<w:DontGrowAutofit /> -</w:Compatibility> -<w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> -</w:WordDocument> -</xml><![endif]--><!--[if gte mso 9]><xml> -<w:LatentStyles DefLockedState="false" LatentStyleCount="156"> -</w:LatentStyles> -</xml><![endif]--><style type="text/css"> -<!-- - /* Style Definitions */ - p.MsoNormal, li.MsoNormal, div.MsoNormal - {mso-style-parent:""; - margin:0cm; - margin-bottom:.0001pt; - mso-pagination:widow-orphan; - font-size:12.0pt; - font-family:"Times New Roman"; - mso-fareast-font-family:"Times New Roman"; - mso-bidi-font-family:"Times New Roman"; - mso-fareast-language:EN-AU; - mso-bidi-language:AR-SA;} -@page Section1 - {size:612.0pt 792.0pt; - margin:72.0pt 90.0pt 72.0pt 90.0pt; - mso-header-margin:36.0pt; - mso-footer-margin:36.0pt; - mso-paper-source:0;} -div.Section1 - {page:Section1;} ---> -</style><!--[if gte mso 10]> -<style> -/* Style Definitions */ -table.MsoNormalTable -{mso-style-name:"Table Normal"; -mso-tstyle-rowband-size:0; -mso-tstyle-colband-size:0; -mso-style-noshow:yes; -mso-style-parent:""; -mso-padding-alt:0cm 5.4pt 0cm 5.4pt; -mso-para-margin:0cm; -mso-para-margin-bottom:.0001pt; -mso-pagination:widow-orphan; -font-size:10.0pt; -font-family:"Times New Roman"; -mso-ansi-language:#0400; -mso-fareast-language:#0400; -mso-bidi-language:#0400;} -</style> -<![endif]-->Carsguide has a wide range of cars for sale.&nbsp; Our reviews, road tests and tools will help you choose the car for you.</p></div> -</div><!-- // .story-block --> - </div><!-- // .module-content --> -</div><!-- // .module.module-promo-block-story --> - - <!-- promo-block-story --> -<div class="module module-promo-block-story mpos-4 mrpos-2 id1225752673856 text-m-win-5000"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.truelocal.com.au/win5k">Win $5000</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> -<!-- promo-block-a --> -<div class="story-block"> - <h4 class="heading"> - <a href="http://www.truelocal.com.au/win5k">Free business listing - Truelocal.com.au</a> - </h4> - <a href="http://www.truelocal.com.au/win5k" class="thumb-link"><img src="http://resources0.news.com.au/images/2009/07/21/1225752/675480-truelocal.jpg" alt="truelocal" class="thumbnail" width="100" height="75" /></a> - <div class="standfirst"><p>List for free to go in the draw to win. Upload content or purchase a paid listing and get more chances to win.</p></div> -</div><!-- // .story-block --> - </div><!-- // .module-content --> -</div><!-- // .module.module-promo-block-story --> - - <!-- promo-block-story --> -<div class="module module-promo-block-story mpos-5 mrpos-1 id1225752677158 text-m-real-estate"> - <div class="module-header"> - <h3 class="heading"><a href="http://www.realestate.com.au/">Real Estate</a></h3> - </div><!-- // .module-header --> - <div class="module-content"> -<!-- promo-block-a --> -<div class="story-block"> - <h4 class="heading"> - <a href="http://www.realestate.com.au/">NEW tools to help you get the best price</a> - </h4> - <a href="http://www.realestate.com.au/" class="thumb-link"><img src="http://resources3.news.com.au/images/2009/07/21/1225752/673747-realestate.jpg" alt="realestate" class="thumbnail" width="74" height="75" /></a> - <div class="standfirst"><p>Know your market with weekly Auction results and information on recent sales in your area. Find out more.</p></div> -</div><!-- // .story-block --> - </div><!-- // .module-content --> -</div><!-- // .module.module-promo-block-story --> - - </div><!-- // .item ipos-1 irpos-1 --> - </div><!-- // .group-content.item-count-1 --> -</div><!-- // .group --> - - </div><!-- // #content-5 --> - </div><!-- // #content --> -<div id="footer"> - <div id="footer-ads"> - <div class="ad ad-leaderboard"> - <div class="ndmadkit ndmadkit-leaderboard"><script type="text/javascript">ndm.kit.leaderboard();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-leaderboard"><script type="text/javascript">ndm.kit.leaderboard();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-leaderboard"><script type="text/javascript">ndm.kit.leaderboard();</script></div><!-- // .ndmadkit --> - </div><!-- // .ad .ad-leaderboard --> - </div><!-- // #footer-ads --> - <div class="footer-tools"> - <ul> - <li class="first tool-mobile"><a href="/mobile">Mobile</a></li> - <li class="tool-rss"><a href="/help/rss">RSS Feeds</a></li> - <li class="tool-newsletter"><a href="http://news.reply.com.au/ni/newspulse.asp" >Newsletters</a></li> - <li class="tool-tips"><a href="/help/storytips">Send Stories</a></li> - <li class="last tool-pics"><a href="/sendusphotos">Send Your Photos</a></li> - </ul> - </div><!-- // .footer-tools --> - <div class="footer-nav"> - <dl > - <dt class=" first "><a href="/help">Help</a></dt> - <!--[if IE]></dl><dl><![endif]--> - <dt ><a href="/help/contactus">Contact Us</a></dt> - <!--[if IE]></dl><dl><![endif]--> - <dt ><a href="http://www.newsspace.com.au/digital" >Advertise with Us</a></dt> - <!--[if IE]></dl><dl><![endif]--> - <dt ><a href="http://searchjobs.careerone.com.au/search/search.cgi?collection=careerone_xml&amp;meta_g_phrase_sand=News%20Digital%20Media" >Job Opportunities</a></dt> - <!--[if IE]></dl><dl><![endif]--> - <dt ><a href="/archives">Archives</a></dt> - <!--[if IE]></dl><dl><![endif]--> - <dt ><a href="/help/sitemap">Sitemap</a></dt> - <!--[if IE]></dl><dl><![endif]--> - <dt class=" last"><a href="/partner-headlines">News Headlines from Our Partners</a></dt> -</dl><!-- // . --> - - </div><!-- // .footer-nav --> - <!-- pagetreepath : / --> - <div class="footer-legals"> - <ul> - <li class="first"><a href="/help/termsconditions">Terms &amp; Conditions</a></li> - <li><a href="/help/privacypolicy">Privacy Policy</a></li> - <li class="last"><a href="/help/accessibility">Accessibility</a></li> - </ul> - <p class="copyright">Copyright 2009 News Limited. All times AEST (GMT +10)..</p> - </div><!-- // .footer-legals --> - <div class="as ad-footer"> - <div class="ndmadkit ndmadkit-footer"><script type="text/javascript">ndm.kit.footer();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-footer"><script type="text/javascript">ndm.kit.footer();</script></div><!-- // .ndmadkit --> - <div class="ndmadkit ndmadkit-footer"><script type="text/javascript">ndm.kit.footer();</script></div><!-- // .ndmadkit --> - </div> -</div><!-- // #footer --> -<div id="stats"> - <!--WEBSIDESTORY CODE HBX2.0 (Universal)--> - <!--COPYRIGHT 1997-2005 WEBSIDESTORY,INC. ALL RIGHTS RESERVED. U.S.PATENT No. 6,393,479B1. MORE INFO:http://websidestory.com/privacy--> - <script type="text/javascript"> - //<![CDATA[ - var _hbEC=0,_hbE=new Array;function _hbEvent(a,b){b=_hbE[_hbEC++]=new Object();b._N=a;b._C=0;return b;} - var hbx=_hbEvent("pv");hbx.vpc="HBX0200u";hbx.gn="ths.news.com.au"; - // BEGIN EDITABLE SECTION - // CONFIGURATION VARIABLES - hbx.acct="DM5703019OAF"; //ACCOUNT NUMBER(S) - hbx.pn="Homepage"; //PAGE NAME(S) - hbx.mlc="/homepage"; //MULTI-LEVEL CONTENT CATEGORY - hbx.pndef="NewsComAu"; //DEFAULT PAGE NAME - hbx.ctdef="full"; //DEFAULT CONTENT CATEGORY - //OPTIONAL PAGE VARIABLES - //ACTION SETTINGS - hbx.fv=""; //FORM VALIDATION MINIMUM ELEMENTS OR SUBMIT FUNCTION NAME - hbx.lt="auto"; //LINK TRACKING - hbx.dlf="n"; //DOWNLOAD FILTER - hbx.dft="n"; //DOWNLOAD FILE NAMING - hbx.elf="n"; //EXIT LINK FILTER - //CUSTOM VARIABLES - hbx.ci=""; //CUSTOMER ID - hbx.hc1=""; //CUSTOM 1 - hbx.hc2=""; //CUSTOM 2 - hbx.hc3=""; //CUSTOM 3 - hbx.hc4=""; //CUSTOM 4 - hbx.hrf=""; //CUSTOM REFERRER - hbx.pec=""; //ERROR CODES - //INSERT CUSTOM EVENTS - //END EDITABLE SECTION - //REQUIRED SECTION. CHANGE "YOURSERVER" TO VALID LOCATION ON YOUR WEB SERVER (HTTPS IF FROM SECURE SERVER) - //]]> - </script> - <script type="text/javascript" src="http://resources1.news.com.au/cs/js/hbx.js"></script> - <!--END WEBSIDESTORY CODE--> - <!-- START Nielsen Online SiteCensus V5.3nse --> - <!-- COPYRIGHT 2008 Nielsen Online --> - <script type="text/javascript"> - var _rsCI="newscorp"; - var _rsCG="0"; - var _rsDN="//secure-au.imrworldwide.com/"; - </script> - <script type="text/javascript" src="http://resources1.news.com.au/cs/js/v53nse.js"></script> - <noscript> - <div><img src="//secure-au.imrworldwide.com/cgi-bin/m?ci=newscorp&amp;cg=0&amp;cc=1" alt=""/></div> - </noscript> - <!-- END Nielsen Online SiteCensus V5.3nse --> - <!-- START TRAKTR --> - <script type="text/javascript"> - //<![CDATA[ - var tracktrSectionName = ndm.page.section.replace(/-/g, "_"); - TRAKTR = window.TRAKTR || {}; - /* editable */ - TRAKTR.site = "NEWS"; - TRAKTR.section = tracktrSectionName; - TRAKTR.pn = "NewsComAu | home | homepage | Homepage"; - TRAKTR.tags = "content:type=homepage"; - /* end editable */ - //]]> - </script> - <script type="text/javascript"> - //<![CDATA[ - (function (a) { - var df=window.ndm||{page:{site:"",section:""}},p=a||"basic:on";window.TRAKTR=window.TRAKTR||{}; - p+=",site:"+(TRAKTR.site||df.page.site||"").toLowerCase(); - p+=",section:"+(TRAKTR.section||df.page.section||"").toLowerCase(); - p+=",plugins:"+(TRAKTR.plugins||"").toLowerCase();p+=",release:"+(TRAKTR.release ||"latest").toLowerCase(); - document.write(unescape("%3Cscript src='//traktr.news.com.au/esi/traktr.js?cfg="+encodeURIComponent(p)+".js'%3E%3C/script%3E")); - }()); - //]]> - </script> - <script type="text/javascript"> - //<![CDATA[ - TRAKTR.pi(); - //]]> - </script> - <noscript> - <div> - <img src="//pt200194.unica.com/ntpagetag.gif?js=0&amp;sitename=news" alt="" /> - <img src="//secure-au.imrworldwide.com/cgi-bin/m?ci=newscorp&amp;cg=0" alt="" /> - </div> - </noscript> - <!-- END TRAKTR --> -</div><!-- // # stats --> -</div><!-- // #page --> -</body> -</html> \ No newline at end of file diff --git a/src/test/resources/htmltests/news-com-au-home.html.gz b/src/test/resources/htmltests/news-com-au-home.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..be0fbdd216340bf56d5b34a35e5eaab8b9b3a49d GIT binary patch literal 31720 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 P{)=~CY2!N?{(Axd^zpBE literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/nyt-article-1.html b/src/test/resources/htmltests/nyt-article-1.html deleted file mode 100644 index de10a9577b..0000000000 --- a/src/test/resources/htmltests/nyt-article-1.html +++ /dev/null @@ -1,948 +0,0 @@ - - - - - - - -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> -<html> -<head> -<title>BP to Replace Hayward as Chief Executive - NYTimes.com</title> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> -<meta name="description" content="Mr. Hayward, the oil giant’s embattled chief executive, will be replaced by Robert Dudley, the American executive who is in charge of BP’s operations in the Gulf of Mexico."> -<meta name="keywords" content="Oil (Petroleum) and Gasoline,Accidents and Safety,Offshore Drilling and Exploration,Executives and Management,Hayward Tony,BP Plc"> -<meta name="ROBOTS" content="NOARCHIVE"> -<meta name="DISPLAYDATE" content="July 25, 2010"> -<meta name="hdl" content="BP to Replace Hayward as Chief Executive"> -<meta name="hdl_p" content="As BP Lays Out Future, It Will Not Include Hayward"> -<meta name="byl" content="By CLIFFORD KRAUSS and JAD MOUAWAD"> -<meta name="lp" content="Mr. Hayward, the oil giant’s embattled chief executive, will be replaced by Robert Dudley, the American executive who is in charge of BP’s operations in the Gulf of Mexico."> -<meta name="cre" content="The New York Times"> -<meta name="edt" content="The New York Times on the Web"> -<meta name="pdate" content="20100725"> -<meta name="ttl" content=""> -<meta name="virtloc" content=""> -<meta name="des" content="Oil (Petroleum) and Gasoline;Accidents and Safety;Offshore Drilling and Exploration;Executives and Management"> -<meta name="per" content="Hayward, Tony"> -<meta name="org" content="BP Plc"> -<meta name="geo" content=""> -<meta name="ticker" content="BP Plc|BP|NYSE"> -<meta name="misspelling" content=""> -<meta name="dat" content="July 25, 2010"> -<meta name="tom" content="News"> -<meta name="cat" content=""> -<meta name="col" content=""> -<meta name="dsk" content="Business / Global Business"> -<meta name="articleid" content="1247468509141"> -<meta name="ARTICLE_TEMPLATE_VERSION" CONTENT="700"> -<meta name="hdr_img" content="/images/article/header/sect_business.gif"> -<meta name="thumbnail" content="images/2010/07/26/business/26bp_art/26bp_art-thumbStandard.jpg"> -<meta name="thumbnail_height" content="75"> -<meta name="thumbnail_width" content="75"> -<meta name="xlarge" content=""> -<meta name="xlarge_height" content=""> -<meta name="xlarge_width" content=""> -<meta name="sectionfront_jsonp" content="http://json8.nytimes.com/pages/business/global/index.jsonp"> -<meta name="CG" content="business"> -<meta name="SCG" content="global"> -<meta name="PT" content="Article"> -<meta name="PST" content="News"> -<link rel="canonical" href="http://www.nytimes.com/2010/07/26/business/global/26bp.html" /> - - -<link rel="stylesheet" type="text/css" href="http://graphics8.nytimes.com/css/0.1/screen/build/article/2.0/business/styles.css"><!--[if IE]> - <style type="text/css"> - @import url(http://graphics8.nytimes.com/css/0.1/screen/common/ie.css); - </style> -<![endif]--> -<!--[if IE 6]> - <style type="text/css"> - @import url(http://graphics8.nytimes.com/css/0.1/screen/common/ie6.css); - </style> -<![endif]--> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/common.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/common/screen/DropDown.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/util/tooltip.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/common/screen/altClickToSearch.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/article/upNext.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/article/articleShare.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/article/comments/crnrXHR.js"></script> -<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/article/articleCommentCount.js"></script> - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> - - - -<body > - - -<a name="top"></a> -<div id="shell"> -<ul id="memberTools"> - -<!-- ADXINFO classification="text_ad" campaign="nyt2010-circ-tr-bar1_international_366RU"--><li><a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Bar1&sn2=1a50cb9d/923a393f&sn1=636d5f07/630a7ca5&camp=nyt2010-circ-tr-bar1_international_366RU&ad=093009-TR_bar1_366RU&goto=https%3A%2F%2Ftimesreader%2Enytimes%2Ecom%2Fwebapp%2Fwcs%2Fstores%2Fservlet%2FTimesReaderOffer%3FstoreId%3D10001%26catalogId%3D10001%26campaignId%3D366RU" target="_blank">Try Times Reader 2.0</a></li> - - - - <li><a href="http://www.nytimes.com/auth/login?URI=http://">Log In</a></li> - <li><a href="http://www.nytimes.com/gst/regi.html">Register Now</a></li> - - -</ul> -<div class="tabsContainer"> -<ul id="mainTabs" class="tabs"> -<li class="first"><a href="http://www.nytimes.com">Home Page</a></li> -<li><a href="http://www.nytimes.com/pages/todayspaper/index.html">Today's Paper</a></li> -<li><a href="http://www.nytimes.com/video">Video</a></li> -<li><a href="http://www.nytimes.com/mostpopular">Most Popular</a></li> -<li><a href="http://topics.nytimes.com/top/reference/timestopics">Times Topics</a></li> -</ul> -</div> -<div id="page" class="tabContent active"> -<div class="clearfix" id="masthead"> - -<div class="singleAd" id="Middle1C"> -<!-- ADXINFO classification="button" campaign="ING_Direct_Q3_2010_01_1428598-nyt1"--><SCRIPT type="text/javascript" SRC="http://ad.doubleclick.net/adj/N3282.nytimes.comSD6440/B3948326.5;p=%99qnz%C8ot;sz=88x31;pc=nyt141145_237411;ord=2010.07.26.00.14.18;click=http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Middle1C&camp=ING_Direct_Q3_2010_01_1428598-nyt1&ad=88x31_sitesearch_NEW_B3948326.5&sn2=8dbb4758/c01e224b&snr=doubleclick&snx=1280100044&sn1=706ff5be/78f5c531&goto="> -</SCRIPT> -<NOSCRIPT> -<A HREF="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Middle1C&sn2=8dbb4758/c01e224b&sn1=ca787673/a8bb851e&camp=ING_Direct_Q3_2010_01_1428598-nyt1&ad=88x31_sitesearch_NEW_B3948326.5&goto=http://ad.doubleclick.net/jump/N3282.nytimes.comSD6440/B3948326.5;p=%99qnz%C8ot;sz=88x31;pc=nyt141145_237411;ord=2010.07.26.00.14.18" TARGET="_blank"> -<IMG SRC="http://ad.doubleclick.net/ad/N3282.nytimes.comSD6440/B3948326.5;p=%99qnz%C8ot;sz=88x31;pc=nyt141145_237411;ord=2010.07.26.00.14.18" - BORDER=0 WIDTH=88 HEIGHT=31 - ALT="Click Here"></A> -</NOSCRIPT> -</div> - -<div id="searchWidget"> -<div class="inlineSearchControl"> -<form enctype="application/x-www-form-urlencoded" action="http://query.nytimes.com/search/sitesearch" method="get" name="searchForm" id="searchForm"> -<input type="hidden" value="full" name="date_select"/> -<label for="searchQuery">Search All NYTimes.com</label> -<input type="text" class="text" value="" size="" name="query" id="searchQuery"/> -<input type="hidden" id="searchAll" name="type" value="nyt"/> -<input id="searchSubmit" title="Search" width="22" height="19" alt="Search" type="image" src="http://graphics8.nytimes.com/images/global/buttons/go.gif"> -</form> -</div> -</div> -<div id="branding" > -<a href="http://www.nytimes.com"><img src="http://graphics8.nytimes.com/images/misc/nytlogo152x23.gif" alt="New York Times" id="NYTLogo"/></a> -</div> - -<h2> - -<a href="http://www.nytimes.com/pages/business/global/index.html">Global Business</a> -<span id="withReutersMastheadLogo"><img src="http://graphics8.nytimes.com/images/misc/with-reuters-masthead-logo.gif" alt="With Reuters" /></span> -</h2> - -</div> -<div class="navigation tabsContainer"> -<ul class="tabs"> -<li id="navWorld" class="first "> -<a href="http://www.nytimes.com/pages/world/index.html">World</a> -</li> <li id="navUs" > -<a href="http://www.nytimes.com/pages/national/index.html">U.S.</a> -</li> <li id="navNyregion" > -<a href="http://www.nytimes.com/pages/nyregion/index.html">N.Y. / Region</a> -</li> <li id="navBusiness" class="selected"> -<a href="http://www.nytimes.com/pages/business/index.html">Business</a> -</li> <li id="navTechnology" > -<a href="http://www.nytimes.com/pages/technology/index.html">Technology</a> -</li> <li id="navScience" > -<a href="http://www.nytimes.com/pages/science/index.html">Science</a> -</li> <li id="navHealth" > -<a href="http://www.nytimes.com/pages/health/index.html">Health</a> -</li> <li id="navSports" > -<a href="http://www.nytimes.com/pages/sports/index.html">Sports</a> -</li> <li id="navOpinion" > -<a href="http://www.nytimes.com/pages/opinion/index.html">Opinion</a> -</li> <li id="navArts" > -<a href="http://www.nytimes.com/pages/arts/index.html">Arts</a> -</li> <li id="navStyle" > -<a href="http://www.nytimes.com/pages/style/index.html">Style</a> -</li> <li id="navTravel" > -<a href="http://www.nytimes.com/pages/travel/index.html">Travel</a> -</li> <li id="navJobs" > -<a href="http://www.nytimes.com/pages/jobs/index.html">Jobs</a> -</li> <li id="navRealestate" > -<a href="http://www.nytimes.com/pages/realestate/index.html">Real Estate</a> -</li> <li id="navAutomobiles" > -<a href="http://www.nytimes.com/pages/automobiles/index.html">Autos</a> -</li></ul> -</div> -<div class="subNavigation tabContent active"> -<div class="column firstColumn"> -<div id="searchWidget"> -<div class="inlineSearchControl"> -<form enctype="application/x-www-form-urlencoded" action="http://query.nytimes.com/search/business/" method="get" name="searchForm" id="searchForm"> -<input type="hidden" value="full" name="date_select"/> -<input id="bsearchQuery" type="text" class="text" name="query" autocomplete="off"/> -<div class="querySuggestions" style="display:none;"></div> -<input type="hidden" id="searchAll" name="type" value="nyt"/> -<input id="searchSubmit" title="Search" width="40" height="19" alt="Search" type="image" src="http://graphics8.nytimes.com/images/global/global_search/search_button40x19.gif"> -</form> -</div> -</div><!--close searchWidget --> -</div><!--close column --> -<div class="column lastColumn"> -<ul class="horizontalMenu wrap"> -<li class="firstItem selected">Global</li> -<li><a href="http://dealbook.blogs.nytimes.com">DealBook</a></li> -<li><a href="http://markets.on.nytimes.com/research/markets/overview/overview.asp">Markets</a></li> -<li><a href="http://www.nytimes.com/pages/business/economy/index.html">Economy</a></li> -<li><a href="http://www.nytimes.com/pages/business/energy-environment/index.html">Energy</a></li> -<li><a href="http://www.nytimes.com/pages/business/media/index.html">Media</a></li> -<li><a href="http://www.nytimes.com/pages/technology/personaltech/index.html">Personal Tech</a></li> -<li><a href="http://www.nytimes.com/pages/business/smallbusiness/index.html">Small Business</a></li> -<li class="lastItem"><a href="http://www.nytimes.com/pages/your-money/index.html">Your Money</a></li> -</ul> -</div><!--close column --> -</div><!--close subNavigation --> - - - -<div class="singleAd" id="TopAd"> -<!-- ADXINFO classification="leaderboard_728" campaign="Google_2010_BIZ_728x90_nyt15"--><div class="clearfix"> -<script type="text/javascript" language="JavaScript"> -<!-- - google_ad_client = 'ca-nytimes_display_html'; - google_alternate_ad_url = 'http://www.nytimes.com/ads/remnant/networkredirect-leaderboard.html'; - google_ad_width = 728; - google_ad_height = 90; - google_ad_format = '728x90_pas_abgc'; - google_ad_type = 'image,flash'; - google_encoding = 'utf8'; - google_safe = 'high'; - google_targeting = 'site'; - google_ad_channel = 'business_leaderboard'; -// --> -</script> -<script type="text/javascript" language="JavaScript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script> -<noscript> - <img height="1" width="1" border="0" src="http://pagead2.googlesyndication.com/pagead/imp.gif?client=ca-nytimes_display_html&event=noscript" /> -</noscript> -<div style="font-family: Arial; font-size: 10px; color:#004276; float: right; margin-right: 125px;"><a href="http://www.nytimes.whsites.net/mediakit/">Advertise on NYTimes.com</a></div></div> - -</div> - - -<div id="main"> -<div class="spanAB wrap closing"> -<div id="abColumn" class="abColumn"><!--open abColumn --> -<div id="article"> -<!--cur: prev:--> -<div class="columnGroup first"> -<h1 class="articleHeadline"><NYT_HEADLINE version="1.0" type=" ">As BP Lays Out Future, It Will Not Include Hayward</NYT_HEADLINE></h1> -<NYT_BYLINE > <h6 class="byline">By <a href="http://topics.nytimes.com/top/reference/timestopics/people/k/clifford_krauss/index.html?inline=nyt-per" title="More Articles by Clifford Krauss" class="meta-per">CLIFFORD KRAUSS</a> and <a href="http://topics.nytimes.com/top/reference/timestopics/people/m/jad_mouawad/index.html?inline=nyt-per" title="More Articles by Jad Mouawad" class="meta-per">JAD MOUAWAD</a></h6> -</NYT_BYLINE> <h6 class="dateline">Published: July 25, 2010</h6> -<script type="text/javascript"> -var articleToolsShareData = {"url":"http:\/\/www.nytimes.com\/2010\/07\/26\/business\/global\/26bp.html","headline":"As BP Lays Out Future, It Will Not Include Hayward","description":"Mr. Hayward, the oil giant\u2019s embattled chief executive, will be replaced by Robert Dudley, the American executive who is in charge of BP\u2019s operations in the Gulf of Mexico.","keywords":"Oil (Petroleum) and Gasoline,Accidents and Safety,Offshore Drilling and Exploration,Executives and Management,Hayward Tony,BP Plc","section":"business","sub_section":"global","section_display":"Business","sub_section_display":"Global Business","byline":"By <a href=\"http:\/\/topics.nytimes.com\/top\/reference\/timestopics\/people\/k\/clifford_krauss\/index.html?inline=nyt-per\" title=\"More Articles by Clifford Krauss\" class=\"meta-per\">CLIFFORD KRAUSS<\/a> and <a href=\"http:\/\/topics.nytimes.com\/top\/reference\/timestopics\/people\/m\/jad_mouawad\/index.html?inline=nyt-per\" title=\"More Articles by Jad Mouawad\" class=\"meta-per\">JAD MOUAWAD<\/a>","pubdate":"July 25, 2010","passkey":null}; -function getShareURL() { - return encodeURIComponent(articleToolsShareData.url); -} -function getShareHeadline() { - return encodeURIComponent(articleToolsShareData.headline); -} -function getShareDescription() { - return encodeURIComponent(articleToolsShareData.description); -} -function getShareKeywords() { - return encodeURIComponent(articleToolsShareData.keywords); -} -function getShareSection() { - return encodeURIComponent(articleToolsShareData.section); -} -function getShareSubSection() { - return encodeURIComponent(articleToolsShareData.sub_section); -} -function getShareSectionDisplay() { - return encodeURIComponent(articleToolsShareData.section_display); -} -function getShareSubSectionDisplay() { - return encodeURIComponent(articleToolsShareData.sub_section_display); -} -function getShareByline() { - return encodeURIComponent(articleToolsShareData.byline); -} -function getSharePubdate() { - return encodeURIComponent(articleToolsShareData.pubdate); -} -function getSharePasskey() { - return encodeURIComponent(articleToolsShareData.passkey); -} -</script> -<div class="articleTools"> -<div class="box"> -<div class="inset"> -<ul id="toolsList" class="toolsList wrap"> -<li class="comments"><a onClick="javascript:dcsMultiTrack('DCS.dcssip','www.nytimes.com','DCS.dcsuri','/article comments/view-tools.html','WT.ti','Article Comments View Tools','WT.z_aca','Tools-View','WT.gcom','Com');" href="http://community.nytimes.com/comments/www.nytimes.com/2010/07/26/business/global/26bp.html" >comments <span id="commentCount"></span></a></li> -<li class="email"> - -<a id="emailThis" onClick="s_code_linktrack('Article-Tool-EmailSignIn');" - href="http://www.nytimes.com/auth/login?URI=http://www.nytimes.com/2010/07/26/business/global/26bp.html">Sign In to E-Mail</a> -</li> -<li class="print"> -<A HREF="/2010/07/26/business/global/26bp.html?hp=&pagewanted=print">Print</a> -</li> - -<A HREF="/2010/07/26/business/global/26bp.html?hp=&pagewanted=all"></a> - -<NYT_REPRINTS_FORM> - -<script name="javascript"> - function submitCCCForm(){ - PopUp = window.open('', '_Icon','location=no,toolbar=no,status=no,width=650,height=550,scrollbars=yes,resizable=yes'); - this.document.cccform.submit(); - } - </script> -<li class="reprints"> -<form name="cccform" action="https://s100.copyright.com/CommonApp/LoadingApplication.jsp" target="_Icon"> -<input type="hidden" name="Title" value="As BP Lays Out Future, It Will Not Include Hayward"> -<input type="hidden" name="Author" value="By CLIFFORD KRAUSS and JAD MOUAWAD "> -<input type="hidden" name="ContentID" value="http://www.nytimes.com/2010/07/26/business/global/26bp.html"> -<input type="hidden" name="FormatType" value="default"> -<input type="hidden" name="PublicationDate" value="July 26, 2010"> -<input type="hidden" name="PublisherName" value="The New York Times"> -<input type="hidden" name="Publication" value="nytimes.com"> -<input type="hidden" name="wordCount" value="12"> -</form> -<a href="#" onClick="submitCCCForm()">Reprints</a> -</li> - -</NYT_REPRINTS_FORM> -</ul> -<div class="articleToolsSponsor" id="Frame4A"><!-- ADXINFO classification="button_120x60" campaign="foxsearch2010_emailtools_1225558c_nyt5"--><a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Frame4A&sn2=a23bc051/6ffe8c2e&sn1=ec77b465/65acc4bd&camp=foxsearch2010_emailtools_1225558c_nyt5&ad=Conviction_120x60_06.18&goto=http%3A%2F%2Fwww%2Efoxsearchlight%2Ecom%2Fconviction" target="_blank"> -<img src="http://graphics8.nytimes.com/adx/images/ADS/23/60/ad.236028/conviction_120x60.gif" width="120" height="60" border="0"></a> -</div> </div> -</div> -</div> -<div class="articleBody"> - - - - - - -<NYT_TEXT > - -<NYT_CORRECTION_TOP> -</NYT_CORRECTION_TOP> - <p> -<a href="http://topics.nytimes.com/top/reference/timestopics/people/h/tony_hayward/index.html?inline=nyt-per" title="More articles about Tony Hayward." class="meta-per">Tony Hayward</a>, the embattled chief executive of <a href="http://topics.nytimes.com/top/news/business/companies/bp_plc/index.html?inline=nyt-org" title="More information about BP P.L.C." class="meta-org">BP</a>, has agreed to step down and be replaced by <a href="http://topics.nytimes.com/top/reference/timestopics/people/d/robert_dudley/index.html?inline=nyt-per" title="More articles about Robert Dudley." class="meta-per">Robert Dudley</a>, the company&rsquo;s most senior American executive who is now in charge of BP&rsquo;s operations in the Gulf of Mexico, according to a person close to the company&rsquo;s board. </p> -</div> -<div class="articleInline runaroundLeft"> - -<!--forceinline--> -<div class="inlineImage module"> -<div class="image"> -<div class="icon enlargeThis"><a href="javascript:pop_me_up2('http://www.nytimes.com/imagepages/2010/07/26/business/26bp_art.html','26bp_art_html','width=431,height=600,scrollbars=yes,toolbars=no,resizable=yes')">Enlarge This Image</a></div> -<a href="javascript:pop_me_up2('http://www.nytimes.com/imagepages/2010/07/26/business/26bp_art.html','26bp_art_html','width=431,height=600,scrollbars=yes,toolbars=no,resizable=yes')"> -<img src="http://graphics8.nytimes.com/images/2010/07/26/business/26bp_art/26bp_art-articleInline.jpg" width="190" height="255" alt=""> -</a> -</div> -<h6 class="credit">Susan Walsh/Associated Press</h6> -<p class="caption">CEO Tony Hayward, left, may be replaced by Bob Dudley, right, BP’s most senior American executive, according to a person close to the board. </p> -</div> - -<div class="columnGroup doubleRule"> -<h3 class="sectionHeader">Related</h3> -<ul class="headlinesOnly multiline flush"> -<li> -<h6><a href="http://www.nytimes.com/2010/07/13/business/energy-environment/13bprisk.html?ref=global"> -In BP’s Record, a History of Boldness and Costly Blunders</a> -(July 13, 2010) -</h6> -</li> -<li> -<h6><a href="http://www.nytimes.com/2010/07/08/business/global/08bp.html?ref=global"> -BP Tries to Reassure Shareholders</a> -(July 8, 2010) -</h6> -</li> -</ul> -</div> -<div id="portfolioInline"> -<h3 class="sectionHeader">Add to Portfolio</h3> -<ul class="flush"> - - - <li><a href="http://www.nytimes.com/auth/login?URI=http://www.nytimes.com/2010/07/26/business/global/26bp.html">BP Plc</a></li> - -</ul> -<p class="refer"><a href="http://markets.on.nytimes.com/research/portfolio/view/view.asp#sda">Go to your Portfolio &#187;</a></p> -</div> - -<div class="inlineImage module"> -<div class="image"> -<img src="http://graphics8.nytimes.com/images/2010/07/26/business/26bp-dudlye/26bp-dudlye-articleInline.jpg" width="190" height="262" alt=""> -</div> -<h6 class="credit">Steven Senne/Associated Press</h6> -<p class="caption">Robert Dudley. </p> -</div> - -</div> -<div id="readerscomment" class="inlineLeft"></div> -<div class="articleBody"> - <p> -The change in leadership will be discussed by the board of directors on Monday and may be announced Tuesday if the board ratifies the decision. Mr. Hayward would probably be replaced in the fall, the person said, but a decision has already been made by mutual agreement between Mr. Hayward and senior BP management. </p><p> -&ldquo;It is in the best interest of the company to go forward with fresh leadership,&rdquo; the person said. </p><p> -Mr. Hayward, who has been running BP since 2007, is the first senior executive at BP to pay the price for the largest <a href="http://topics.nytimes.com/top/reference/timestopics/subjects/o/oil_spills/gulf_of_mexico_2010/index.html?inline=nyt-classifier" title="More articles about oil spills." class="meta-classifier">oil spill</a> in the United States, after the Deepwater Horizon blew up on April 20. His handling of the crisis has infuriated Gulf Coast residents and government officials alike, especially after a series of public gaffes forced him to retreat from the spotlight. </p><p> -A great deal is at stake for BP, which remains under considerable pressure even though the oil has stopped gushing from its well underneath the Gulf of Mexico. A tropical storm over the weekend briefly forced BP to suspend operations to permanently plug the doomed well. Some members of Congress want to ban BP from running new offshore ventures. The Senate, meanwhile, is expected to vote on legislation this week that would hold &ldquo;BP accountable,&rdquo; said Senator <a href="http://topics.nytimes.com/top/reference/timestopics/people/r/harry_reid/index.html?inline=nyt-per" title="More articles about Harry Reid." class="meta-per">Harry Reid</a>, the Democratic majority leader. </p><p> -And the company continues to lurch from one public relations embarrassment to another. Last week, BP admitted it posted doctored pictures of its spill operations on its corporate Web site. </p><p> -And on Saturday, <a href="http://topics.nytimes.com/top/reference/timestopics/people/f/kenneth_r_feinberg/index.html?inline=nyt-per" title="More articles about Kenneth R. Feinberg." class="meta-per">Kenneth Feinberg</a>, the administrator managing the $20 billion claims fund BP set up under pressure from the White House, accused the company of holding up compensation payments to spill victims. </p><p> -&ldquo;I have a concern that BP is stalling claims,&rdquo; Mr. Feinberg told reporters. &ldquo;I doubt they are stalling for money. It&rsquo;s not that. I just don&rsquo;t think they know the answers to the questions&rdquo; by the claimants. </p><p> -This uncertainty about BP&rsquo;s future business, its ultimate liabilities and its public relations debacle continue to weigh on the company&rsquo;s share price, which is down about 40 percent since the spill started. </p><p> -&ldquo;The key issue now is whether investors and BP&rsquo;s board think Tony Hayward is the right person to move the company forward,&rdquo; said Matthew J. Slaughter, a professor at <a href="http://topics.nytimes.com/top/reference/timestopics/organizations/d/dartmouth_college/index.html?inline=nyt-org" title="More articles about Dartmouth College" class="meta-org">Dartmouth College</a>&rsquo;s Tuck School of Business. &ldquo;Is this a BP problem or is this a Tony Hayward problem?&rdquo; </p><p> -Regardless of who leads the company, BP&rsquo;s top executives have a lot to tackle. They need to convince the company&rsquo;s constituents &mdash; its shareholders, regulators and government officials in the United States and other countries where BP has operations &mdash; that BP can pay all costs related to the spill, clean up the Gulf Coast, and still manage to grow its business around the world, analysts said. </p><p> -In recent weeks, BP has taken steps to put the oil spill behind it. It has been busy negotiating the sale of some of its assets in Texas, Egypt and Canada to the <a href="http://topics.nytimes.com/top/news/business/companies/apache_corporation/index.html?inline=nyt-org" title="More information about Apache Corporation" class="meta-org">Apache Corporation</a>, raising $7 billion. Mr. Hayward personally sought to reassure key officials in Russia and Azerbaijan, where BP has large operations, that the company was still a reliable and safe partner. </p><p> -BP also recently announced that it had won new concessions to drill offshore Egypt; alongside <a href="http://topics.nytimes.com/top/news/business/companies/chevron_corporation/index.html?inline=nyt-org" title="More information about Chevron Corp" class="meta-org">Chevron</a>, it reportedly bid for an offshore exploration block in the South China Sea; and this month, it spent nearly $100 million to buy a cellulosic biofuel business. </p><p> -In the gulf, there was positive news when BP finally managed to stem the flow of oil with a new cap. The company hopes to permanently shut the well within the next few weeks. </p><p> -Bruce Lanni, an energy portfolio strategist at Nollenberger Capital Partners, said the fact that no more oil was spilling the gulf was &ldquo;an inflection point&rdquo; for BP. </p><p> -&ldquo;There are a lot of good things now going in BP&rsquo;s favor,&rdquo; Mr. Lanni said. &ldquo;There has been an overreaction to the cost of the spill. BP has the opportunity to emerge as a stronger company. I think this is where investors are missing a window of opportunity.&rdquo; </p><p> -Some investors, however, are still concerned about the ultimate price tag for the spill. Uncertainty over BP&rsquo;s liabilities is keeping its shares under considerable pressure, although they rebounded somewhat in recent weeks. BP stock closed at $36.86 on Friday, valuing the company at $115 billion. </p><p> -&ldquo;Right now the market is just guessing what the liability might be for BP,&rdquo; Jay Singhania, a vice president at Westwood Management. &ldquo;If BP could help outline exactly what the costs would be, then investors could gain more confidence.&rdquo; </p><p> -The final bill will depend, in part, on whether the investigation determines that BP was negligent and responsible for the blowout at its Macondo oil well. The explosion on the Deepwater Horizon drilling rig on April 20 killed 11 workers. </p><p> -So far, BP said it has spent $3.95 billion on its containment and clean up efforts. The company warned in a statement, &ldquo;It is too early to quantify other potential costs and liabilities associated with the incident.&rdquo; </p><p> -Investors and analysts also insisted that BP must rapidly tackle its safety record head-on. &ldquo;It is very clear BP will need to show that its management practices will improve and instill an greater safety discipline within the organization,&rdquo; said Catharina Milostan, an energy analyst at <a href="http://topics.nytimes.com/top/news/business/companies/morningstar-inc/index.html?inline=nyt-org" title="More information about Morningstar Incorporated" class="meta-org">Morningstar</a>. </p><p> -Mr. Hayward, a geologist who began his career at BP and became chief executive in 2007, inherited a company with a poor safety record that resulted in a string of deadly accidents and spills in the United States. He sought to change the &ldquo;top-down&rdquo; style of management while trying to simplify its structure. Last year, he said it would take five years to change BP&rsquo;s culture and adopt a single, companywide operating system. </p><NYT_AUTHOR_ID> <div class="authorIdentification"> -<p><p>Julia Werdigier contributed reporting.</p></p> -</div> -</NYT_AUTHOR_ID><NYT_CORRECTION_BOTTOM> <div class="articleCorrection"> -</div> -</NYT_CORRECTION_BOTTOM><NYT_UPDATE_BOTTOM> -</NYT_UPDATE_BOTTOM> -</NYT_TEXT> -</div> </div> -<!--cur: prev:--> -<div class="columnGroup "> -<div class="articleFooter"> -<div class="articleMeta"> -<div class="opposingFloatControl wrap"> -</div> -</div> -</div> </div> -<!--cur: prev:--> -<div class="columnGroup last"> -<div id="articleExtras"> -<div class="expandedToolsRight"> -<div class="articleTools"> -<div class="box"> -<div class="inset"> -<ul id="toolsList" class="toolsList wrap"> -<li class="comments"><a onClick="javascript:dcsMultiTrack('DCS.dcssip','www.nytimes.com','DCS.dcsuri','/article comments/view-tools.html','WT.ti','Article Comments View Tools','WT.z_aca','Tools-View','WT.gcom','Com');" href="http://community.nytimes.com/comments/www.nytimes.com/2010/07/26/business/global/26bp.html" >comments <span id="commentCount"></span></a></li> -<li class="email"> -<a id="emailThis" onClick="s_code_linktrack('Article-Tool-EmailSignIn');" - href="http://www.nytimes.com/auth/login?URI=http://www.nytimes.com/2010/07/26/business/global/26bp.html">Sign In to E-Mail</a> -</li> -<li class="print"> -<A HREF="/2010/07/26/business/global/26bp.html?hp=&pagewanted=print">Print</a> -</li> - -<A HREF="/2010/07/26/business/global/26bp.html?hp=&pagewanted=all"></a> - -<script name="javascript"> - function submitCCCForm(){ - PopUp = window.open('', '_Icon','location=no,toolbar=no,status=no,width=650,height=550,scrollbars=yes,resizable=yes'); - this.document.cccform.submit(); - } - </script> -<li class="reprints"> -<a href="#" onClick="submitCCCForm()">Reprints</a> -</li> -</ul> - </div> -</div> -</div> -<script type="text/javascript"> -writePost(); -</script> -</div> -</div> - -<div class="singleAd" id="Bottom1"> -<!-- ADXINFO classification="text_ad" campaign="nyt2010-circ-tr-intl-footer-nonhp-36UYL"--><p><A HREF="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Bottom1&sn2=794b505b/5ccf9ae2&sn1=cf6ed596/83dbe05f&camp=nyt2010-circ-tr-intl-footer-nonhp-36UYL&ad=051810-tr-intl-footer-nonhp-36UYL&goto=https%3A%2F%2Ftimesreader%2Enytimes%2Ecom%2Fwebapp%2Fwcs%2Fstores%2Fservlet%2FTimesReader%3FstoreId%3D10001%26catalogId%3D10001%26campaignId%3D36UYL" target="_blank">Times Reader 2.0: Daily delivery of The Times - straight to your computer. Subscribe for just $4.62 a week.</a></p> - - -</div> - - -<div id="relatedArticles" class="list"><h3>Past Coverage</h3><ul><li><span class="headlineWrapper"><a onClick="s_code_linktrack('Article-RelatedArticles-searchFree');" href="http://www.nytimes.com/2010/07/13/business/energy-environment/13bprisk.html?fta=y">In BP's Record, a History Of Boldness and Blunders</a></span>&nbsp;(July 13, 2010)</li><li><span class="headlineWrapper"><a onClick="s_code_linktrack('Article-RelatedArticles-searchFree');" href="http://www.nytimes.com/2010/07/08/business/global/08bp.html?fta=y">BP Begins Its Next Challenge: Reassuring Investors</a></span>&nbsp;(July 8, 2010)</li><li><span class="headlineWrapper"><a onClick="s_code_linktrack('Article-RelatedArticles-searchFree');" href="http://www.nytimes.com/2010/06/25/us/25liability.html?fta=y">NEWS ANALYSIS; Liability Issues Loom, for BP et al.</a></span>&nbsp;(June 25, 2010)</li><li><span class="headlineWrapper"><a onClick="s_code_linktrack('Article-RelatedArticles-searchFree');" href="http://www.nytimes.com/2010/06/23/business/23dudley.html?fta=y">Into the Line of Fire</a></span>&nbsp;(June 23, 2010)</li></ul></div> -<div class="relatedSearchesModule"> -<h5 class="sectionHeaderSm">Related Searches</h5> -<form ACTION="/mem/tnt.html" METHOD="GET" ENCTYPE="application/x-www-form-urlencoded"> -<ul class="opposingFloatControl wrap"> -<input type="hidden" name="retA" value="http://www.nytimes.com//2010/07/26/business/global/26bp.html"/> -<input type="hidden" name="retT" value="As BP Lays Out Future, It Will Not Include Hayward"/> -<input type="hidden" name="module" value="call"/> -<input type="hidden" name="alert_context" value="1"/> -<input type="hidden" name="topic1" value="BP+Plc"/> -<input type="hidden" name="topic_field1" value="org"/> -<li class="clearfix"> -<span class="element1"><a href="http://query.nytimes.com/search/query&#063;ppds=org&#038;v1=BP+Plc&#038;fdq=19960101&#038;td=sysdate&#038;sort=newest&#038;ac=&#038;rt=1%2Cdes%2Corg%2Cper%2Cgeo" onClick="javascript:s_code_linktrack('Article-RelatedTopics');">BP Plc</a></span> -<span class="emailAlert element2 meta icon"> -<a href="http://select.nytimes.com/mem/tnt.html?module=call&alert_context=1&topic1=BP+Plc&topic_field1=org&topic1_check=y&retA=&retT=&cskey=" onClick="javascript:s_code_linktrack('Article-RelatedTopics'); dcsMultiTrack('DCS.dcssip','www.nytimes.com','DCS.dcsuri','/newstracker/add.html','WT.ti','Newstracker Add','WT.z_nta','Add','WT.pers','Per','WT.z_dcsm','1');">Get E-Mail Alerts</a> -</span> -</li> -<input type="hidden" name="topic1" value="Hayward%2C+Tony"/> -<input type="hidden" name="topic_field1" value="per"/> -<li class="clearfix"> -<span class="element1"><a href="http://query.nytimes.com/search/query&#063;ppds=per&#038;v1=Hayward%2C+Tony&#038;fdq=19960101&#038;td=sysdate&#038;sort=newest&#038;ac=&#038;rt=1%2Cdes%2Corg%2Cper%2Cgeo" onClick="javascript:s_code_linktrack('Article-RelatedTopics');">Hayward, Tony</a></span> -<span class="emailAlert element2 meta icon"> -<a href="http://select.nytimes.com/mem/tnt.html?module=call&alert_context=1&topic1=Hayward%2C+Tony&topic_field1=per&topic1_check=y&retA=&retT=&cskey=" onClick="javascript:s_code_linktrack('Article-RelatedTopics'); dcsMultiTrack('DCS.dcssip','www.nytimes.com','DCS.dcsuri','/newstracker/add.html','WT.ti','Newstracker Add','WT.z_nta','Add','WT.pers','Per','WT.z_dcsm','1');">Get E-Mail Alerts</a> -</span> -</li> -<input type="hidden" name="topic1" value="Oil+%28Petroleum%29+and+Gasoline"/> -<input type="hidden" name="topic_field1" value="des"/> -<li class="clearfix"> -<span class="element1"><a href="http://query.nytimes.com/search/query&#063;ppds=des&#038;v1=Oil+%28Petroleum%29+and+Gasoline&#038;fdq=19960101&#038;td=sysdate&#038;sort=newest&#038;ac=&#038;rt=1%2Cdes%2Corg%2Cper%2Cgeo" onClick="javascript:s_code_linktrack('Article-RelatedTopics');">Oil (Petroleum) and Gasoline</a></span> -<span class="emailAlert element2 meta icon"> -<a href="http://select.nytimes.com/mem/tnt.html?module=call&alert_context=1&topic1=Oil+%28Petroleum%29+and+Gasoline&topic_field1=des&topic1_check=y&retA=&retT=&cskey=" onClick="javascript:s_code_linktrack('Article-RelatedTopics'); dcsMultiTrack('DCS.dcssip','www.nytimes.com','DCS.dcsuri','/newstracker/add.html','WT.ti','Newstracker Add','WT.z_nta','Add','WT.pers','Per','WT.z_dcsm','1');">Get E-Mail Alerts</a> -</span> -</li> -<input type="hidden" name="topic1" value="Accidents+and+Safety"/> -<input type="hidden" name="topic_field1" value="des"/> -<li class="clearfix"> -<span class="element1"><a href="http://query.nytimes.com/search/query&#063;ppds=des&#038;v1=Accidents+and+Safety&#038;fdq=19960101&#038;td=sysdate&#038;sort=newest&#038;ac=&#038;rt=1%2Cdes%2Corg%2Cper%2Cgeo" onClick="javascript:s_code_linktrack('Article-RelatedTopics');">Accidents and Safety</a></span> -<span class="emailAlert element2 meta icon"> -<a href="http://select.nytimes.com/mem/tnt.html?module=call&alert_context=1&topic1=Accidents+and+Safety&topic_field1=des&topic1_check=y&retA=&retT=&cskey=" onClick="javascript:s_code_linktrack('Article-RelatedTopics'); dcsMultiTrack('DCS.dcssip','www.nytimes.com','DCS.dcsuri','/newstracker/add.html','WT.ti','Newstracker Add','WT.z_nta','Add','WT.pers','Per','WT.z_dcsm','1');">Get E-Mail Alerts</a> -</span> -</li> -</ul> -</form> -</div> -</div> -</div> -</div><!--close abColumn --> -<div class="cColumn"> - -<div class="columnGroup"> - -</div> -<!--cur: prev:--> -<div class="columnGroup first"> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -<div class="singleAd" id="Box3"> -<!-- ADXINFO classification="feature_squares" campaign="regilite-P2-todaysheadlines-hardnews"--><IFRAME title="regilite" src="http://www.nytimes.com/gst/litesub_insert.html?product=TH&size=336X90" width="336" height="90" marginheight="0" marginwidth="0" frameborder="0" vspace="0" hspace="0" scrolling="no"></IFRAME> -</div> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -<div class="singleAd" id="MiddleRight"> -<!-- ADXINFO classification="bigad" campaign="Charles_Schwab_Q3-2010_02_1426486-nyt1"--><SCRIPT type="text/javascript" SRC="http://ad.doubleclick.net/adj/N6036.6440.NYTIMES/B4617983;sz=300x250;pc=nyt140815_236716;ord=2010.07.26.00.14.18;click=http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=MiddleRight&camp=Charles_Schwab_Q3-2010_02_1426486-nyt1&ad=300x250_Remnant_Biz_B4617983&sn2=48a3b0af/206c74ae&snr=doubleclick&snx=1280100059&sn1=8b12e31f/f41e9f0a&goto="></SCRIPT> -<NOSCRIPT> -<A HREF="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=MiddleRight&sn2=48a3b0af/206c74ae&sn1=307057ad/c21f24ee&camp=Charles_Schwab_Q3-2010_02_1426486-nyt1&ad=300x250_Remnant_Biz_B4617983&goto=http://ad.doubleclick.net/jump/N6036.6440.NYTIMES/B4617983;sz=300x250;pc=nyt140815_236716;ord=2010.07.26.00.14.18" TARGET="_blank"> -<IMG SRC="http://ad.doubleclick.net/ad/N6036.6440.NYTIMES/B4617983;sz=300x250;pc=nyt140815_236716;ord=2010.07.26.00.14.18" - BORDER=0 WIDTH=300 HEIGHT=250 - ALT="Click Here"></A> -</NOSCRIPT> -</div> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> -<div id="mostPopWidget" class="singleRule"> - - <!-- MOST POPULAR MODULE STARTS --> - <h4>MOST POPULAR - BUSINESS</h4> - <div id="tabsContainer"> - <ul class="tabs"> - <li class="selected"><a href="#">E-Mailed</a></li> - <li><a href="#">Blogged</a></li> - - <li><a href="#">Viewed</a></li> - - </ul> - </div> - <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"></HEAD><div class="tabContent tabContentActive" id="mostEmailed"><ol> -<li><a href="http://www.nytimes.com/2010/07/25/business/global/25chocolate.html?src=me&amp;ref=business" title="Click to go to this article">Trader&rsquo;s Cocoa Binge Wraps Up Chocolate Market</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25elon.html?src=me&amp;ref=business" title="Click to go to this article">Tesla Electric Cars: Revved Up, but Far to Go</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/energy-environment/24gas.html?src=me&amp;ref=business" title="Click to go to this article">E.P.A. Considers Risks of Gas Extraction</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/24wealth.html?src=me&amp;ref=business" title="Click to go to this article">Wealth Matters: If It Causes Stress, Is It Really a Vacation Home?</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/24nocera.html?src=me&amp;ref=business" title="Click to go to this article">Talking Business: Credit Score Is the Tyrant in Lending</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25digi.html?src=me&amp;ref=business" title="Click to go to this article">Digital Domain: Even With All Its Profits, Microsoft Has a Popularity Problem</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/media/24schorr.html?src=me&amp;ref=business" title="Click to go to this article">Daniel Schorr, Journalist, Dies at 93</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25gret.html?src=me&amp;ref=business" title="Click to go to this article">Fair Game: Seeing vs. Doing</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25corner.html?src=me&amp;ref=business" title="Click to go to this article">Corner Office: Always Keep a Few Tricks Up Your Sleeve</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/24insure.html?src=me&amp;ref=business" title="Click to go to this article">For Insurers, Fight Is Now Over Details</a></li> -</ol> - <a class="more" href="http://www.nytimes.com/gst/mostemailed.html">Go to Complete List &#x00bb;</a> - </div><!-- #most emailed top10 --> - - <div class="tabContent" id="mostBlogged"> -<ol> -<li><a href="http://www.nytimes.com/2010/07/19/business/media/19press.html?bl" title="Click to go to this article">In Online Journalism, Burnout Starts Younger</a></li> -<li><a href="http://www.nytimes.com/2010/07/21/business/economy/21leonhardt.html?bl" title="Click to go to this article">Overcome by Heat and Inertia</a></li> -<li><a href="http://www.nytimes.com/2010/07/19/business/19training.html?bl" title="Click to go to this article">Job Training Struggles to Keep Pace in an Economy in Flux</a></li> -<li><a href="http://www.nytimes.com/2010/07/20/business/20maywood.html?bl" title="Click to go to this article">A City Outsources Everything. Sky Does Not Fall</a></li> -<li><a href="http://www.nytimes.com/2010/07/22/business/22regulate.html?bl" title="Click to go to this article">Obama Signs Overhaul of Financial System</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/media/24schorr.html?bl" title="Click to go to this article">Daniel Schorr, Journalist, Dies at 93</a></li> -<li><a href="http://www.nytimes.com/2010/07/18/business/18view.html?bl" title="Click to go to this article">What Germany Has Learned About Debt</a></li> -<li><a href="http://www.nytimes.com/2010/07/22/business/22fed.html?bl" title="Click to go to this article">Fed Chief Says Recovery Continues but Outlook Is Uncertain</a></li> -<li><a href="http://www.nytimes.com/2010/07/19/business/19autos.html?bl" title="Click to go to this article">TARP Audit Questions Rush to Close Auto Dealers</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/global/25chocolate.html?bl" title="Click to go to this article">In Trader's Cocoa Binge, Fear for Chocolate Prices</a></li> -</ol> -<a class="more" href="http://www.nytimes.com/gst/mostblogged.html">Go to Complete List &#x00bb;</a> -</div><!-- #most blogged top10 --> - - - <div class="tabContent" id="mostViewed"><ol> -<li><a href="http://www.nytimes.com/2010/07/25/business/global/25chocolate.html?src=mv&amp;ref=business" title="Click to go to this article">Trader&rsquo;s Cocoa Binge Wraps Up Chocolate Market</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25elon.html?src=mv&amp;ref=business" title="Click to go to this article">Tesla Electric Cars: Revved Up, but Far to Go</a></li> -<li><a href="http://www.nytimes.com/2010/07/26/business/global/26bp.html?src=mv&amp;ref=business" title="Click to go to this article">As BP Lays Out Future, It Will Not Include Hayward</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25zynga.html?src=mv&amp;ref=business" title="Click to go to this article">Will Zynga Become the Google of Games?</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25digi.html?src=mv&amp;ref=business" title="Click to go to this article">Digital Domain: Even With All Its Profits, Microsoft Has a Popularity Problem</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/media/24mag.html?src=mv&amp;ref=business" title="Click to go to this article">Cond&eacute; Nast Is Changing Its Blueprint</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/energy-environment/24gas.html?src=mv&amp;ref=business" title="Click to go to this article">E.P.A. Considers Risks of Gas Extraction</a></li> -<li><a href="http://bucks.blogs.nytimes.com/2010/07/25/how-to-lower-a-doctors-bill/?src=mv&amp;ref=business" title="Click to go to this article">One Way to Lower a Doctor's Bill</a></li> -<li><a href="http://www.nytimes.com/2010/07/25/business/25gret.html?src=mv&amp;ref=business" title="Click to go to this article">Fair Game: Seeing vs. Doing</a></li> -<li><a href="http://www.nytimes.com/2010/07/24/business/24wealth.html?src=mv&amp;ref=business" title="Click to go to this article">Wealth Matters: If It Causes Stress, Is It Really a Vacation Home?</a></li> -</ol> - </div><!-- #most viewed top10 --> - - - <script type="text/javascript">new Accordian("mostPopWidget");</script> - <!-- MOST POPULAR MODULE ENDS --> - - -</div><!--close mostPopWidget --> -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -<div class="bigAd" id="Box1"> -<!-- ADXINFO classification="feature_position" campaign="NYT2010_marketingmodule_Style"--><!-- MARKETING MODULE --> -<div style="border:solid #999;border-width:1px;font-family:Arial,sans-serif;text-align:left; width:334px;background:#fff;" class="clearfix wrap"> - <a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=f5c7c9e3/987faa64&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=http://www.nytimes.com/2010/07/22/fashion/22SIXERS.html%3Fex=1295409600%26en=d6ea053bb422f6c0%26ei=5087%26WT.mc_id=ST-D-I-NYT-MOD-MOD-M159-ROS-0710-PH%26WT.mc_ev=click" target="_new"><img src="http://graphics8.nytimes.com/ads/marketing/mm10/style_072510.jpg" width="334" height="154" border="0" alt=""></a> - <div style="padding:7px 9px 0;background:#fff"> - <h2 style="font-size:22px;line-height:24px; margin:0;padding:0 0 4px;"><a style="color:#794951;" target="_new" href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=f5c7c9e3/987faa64&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=http://www.nytimes.com/2010/07/22/fashion/22SIXERS.html%3Fex=1295409600%26en=d6ea053bb422f6c0%26ei=5087%26WT.mc_id=ST-D-I-NYT-MOD-MOD-M159-ROS-0710-HDR%26WT.mc_ev=click">Shoppers on a "diet"</a></h2> - <p style="margin:0 0 3px; padding:0;"><a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=f5c7c9e3/987faa64&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=http://www.nytimes.com/pages/style/index.html%3FWT.mc_id=ST-D-I-NYT-MOD-MOD-M159-ROS-0710-URL%26WT.mc_ev=click" target="_new" style="font-size:11px;margin:3px 0;padding:0;font-family:Arial,sans-serif; color:#000; text-transform:uppercase;">Also in Style &raquo;</a></p> - <ul style="font-size:12px;margin:0; padding-bottom: 10px; border-bottom:1px solid #ccc;" class="refer"> - <li style="font-size:12px"><a target="_new" href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=f5c7c9e3/987faa64&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=http://www.nytimes.com/2010/07/22/fashion/22date.html%3Fex=1295409600%26en=b58a31597b16d64b%26ei=5087%26WT.mc_id=ST-D-I-NYT-MOD-MOD-M159-ROS-0710-L1%26WT.mc_ev=click">The new dating tools: a card and a wink</a></li> - <li style="font-size:12px"><a target="_new" href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=bc2012/52a5c806&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=https://twitter.com/nytimesstyle">Follow Style on Twitter</a></li> - </ul> - </div> - - <div style="padding:5px 9px; float:left; width:316px; background:#fff"> <a style="float:left" href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=215d2ec3/b2a4d29a&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=http://nytimes.com/%3FWT.mc_id=ST-D-I-NYT-MOD-MOD-M159-ROS-0710-LOGO%26WT.mc_ev=click" target="_new"><img src="http://graphics8.nytimes.com/ads/marketing/mm09/verticalst/nytimes.gif" alt="nytimes.com" width="116" height="18" border="0"></a><a style="float:right" href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Box1&sn2=4b9ff321/390fe73f&sn1=f5c7c9e3/987faa64&camp=NYT2010_marketingmodule_Style&ad=ST-D-I-NYT-MOD-MOD-M159-ROS-0710&goto=http://www.nytimes.com/pages/style/index.html%3FWT.mc_id=ST-D-I-NYT-MOD-MOD-M159-ROS-0710-VRT%26WT.mc_ev=click" target="_new"><img src="http://graphics8.nytimes.com/ads/marketing/mm09/verticalst/verticals_style.gif" alt="Style" width="120" height="18" border="0"></a></div><br clear="all"> -</div> - <!-- /MARKETING MODULE --> - -</div> - -</div> -<!--cur: prev:--> -<div class="columnGroup "> -<!--[TwoColumnAdLeft - Begin] --> - <div class="adHeader"> -<h4> -Advertisements </h4> -</div> -<div class="cColumn-TextAdsBox"> - <div class="cColumn-TextAdsLeft"> -<div class="cColumn-TextAd"> -<!-- ADXINFO classification="text_ad" campaign="default-textlink-right5A-RE-cla"--><!-- start text link --> -<a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Right5A&sn2=b4549068/9dda6189&sn1=773bf90d/712d5c19&camp=default-textlink-right5A-RE-cla&ad=textlink-FindDreamHm&goto=http%3A%2F%2Fwww%2Enytimes%2Ecom%2Fpages%2Frealestate%2Findex%2Ehtml&query=2010.07.26.00.14.18" target="_blank"><div class="cColumn-TextAdsHeader"></div><br>Find your dream home with<br>The New York Times Real Estate</a><br> -<!-- end text link --> </div> -<div class="cColumn-TextAd"> -<!-- ADXINFO classification="text_ad" campaign="default-textlink-right6A-RE-cla"--><!-- start text link --> -<a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Right6A&sn2=da1a4854/9dda6188&sn1=480995f9/56cc5144&camp=default-textlink-right6A-RE-cla&ad=textlink-TwitterFollow&goto=http%3A%2F%2Fwww%2Etwitter%2Ecom%2Fnytimes&query=2010.07.26.00.14.18" target="_blank"><div class="cColumn-TextAdsHeader"></div><br>Follow The New York Times on Twitter</a><br> -<!-- end text link --> </div> -<div class="cColumn-TextAd"> -<!-- ADXINFO classification="text_ad" campaign="default-textlink-right7A-RE-cla"--><!-- start text link --> -<a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Right7A&sn2=d05240df/9dda618b&sn1=e500268f/1cb94289&camp=default-textlink-right7A-RE-cla&ad=textlink-The-New-Issue-of-T&goto=http%3A%2F%2Fwww%2Enytimes%2Ecom%2Ftmagazine&query=2010.07.26.00.14.18" target="_blank"><div class="cColumn-TextAdsHeader"></div><br>The new issue of T is here</a><br> -<!-- end text link --> </div> -<div class="cColumn-TextAd"> -<!-- ADXINFO classification="text_ad" campaign="default-textlink-right8A-RE-cla"--><!-- start text link --> -<a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Right8A&sn2=6be92090/9dda618a&sn1=deb940de/bb53afc&camp=default-textlink-right8A-RE-cla&ad=textlink-TimesCastVideo&goto=http%3A%2F%2Fvideo%2Enytimes%2Ecom%2Fvideo%2Fplaylist%2Ftimescast%2F1247467375115%2Findex%2Ehtml&query=2010.07.26.00.14.18" target="_blank"><div class="cColumn-TextAdsHeader"></div><br>See the news in the making. Watch TimesCast, a daily news video.</a><br> -<!-- end text link --> </div> -</div> - <div class="cColumn-TextAdsRight"> -<!-- ADXINFO classification="feature_position" campaign="NYT2010-Knowledge-Network-S4D-ROS"--><a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Bottom3&sn2=2a794b83/5ccf9ada&sn1=b3ddb2b2/a7538cf6&camp=NYT2010-Knowledge-Network-S4D-ROS&ad=86x60_green.gif&goto=http%3A%2F%2Fwww%2Enytimesknownow%2Ecom%2F" target="_blank"> -<img src="http://graphics8.nytimes.com/adx/images/ADS/22/38/ad.223874/86x60_green.gif" width="86" height="60" border="0"></a> - </div> -</div> -<!--[TwoColumnAdLeft - End] --> -</div> -<!--cur: prev:--> -<div class="columnGroup "> - -<div class="singleAd" id="Middle5"> -<!-- ADXINFO classification="feature_position" campaign="IHT2009-Mktg-336x79-US_ROS-Intl-Circ"--><a href="http://www.nytimes.com/adx/bin/adx_click.html?type=goto&opzn&page=www.nytimes.com/yr/mo/day/business&pos=Middle5&sn2=600f1156/e6b1b741&sn1=30de58c8/6d8ea029&camp=IHT2009-Mktg-336x79-US_ROS-Intl-Circ&ad=336x79enjoy&goto=http%3A%2F%2Fsubs%2Eiht%2Ecom" target="_blank"> -<img src="http://graphics8.nytimes.com/adx/images/ADS/22/53/ad.225345/IHT2956_Enjoyv2_336x79.gif" width="336" height="79" border="0"></a> - -</div> - -</div> -<!--cur: prev:--> -<div class="columnGroup last"> - -</div> -<div class="columnGroup"> - -</div> - - - - - - -</div> -</div><!--close spanAB --> - - <!-- start MOTH --> - <div id="insideNYTimes" class="doubleRule"> - <script type="text/javascript" src="http://graphics8.nytimes.com/js/app/moth/moth.js"></script> - <div id="insideNYTimesHeader"> - <div class="navigation"><span id="leftArrow"><img id="mothReverse" src="http://graphics8.nytimes.com/images/global/buttons/moth_reverse.gif" /></span>&nbsp;<span id="rightArrow"><img id="mothForward" src="http://graphics8.nytimes.com/images/global/buttons/moth_forward.gif" /></span></div> - <h4> - Inside NYTimes.com </h4> - </div> - - - <div id="insideNYTimesScrollWrapper"> - <table id="insideNYTimesBrowser" cellspacing="0"> - <tbody> - <tr> - <td class="first"> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/arts/television/index.html">Television &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/arts/television/25joey.html"><img src="http://graphics8.nytimes.com/images/2010/07/25/arts/television/25moth_joey/25moth_joey-moth.jpg" alt="Friend or Faux: Not Quite Joey, but Not Himself" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/arts/television/25joey.html">Friend or Faux: Not Quite Joey, but Not Himself</a></h6> - </div> - </td> - <td> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/opinion/index.html">Opinion &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/opinion/25schmidt.html"><img src="http://graphics8.nytimes.com/images/2010/07/25/opinion/25moth_oped2/25moth_oped2-moth.jpg" alt="Op-Ed: Memories on the Half Shell" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/opinion/25schmidt.html">Op-Ed: Memories on the Half Shell</a></h6> - </div> - </td> - <td> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/fashion/index.html">Fashion & Style &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/pages/fashion/weddings/index.html"><img src="http://graphics8.nytimes.com/images/2010/07/25/fashion/25moth_vows/25moth_vows-moth.jpg" alt="Weddings and Celebrations" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/pages/fashion/weddings/index.html">Weddings and Celebrations</a></h6> - </div> - </td> - <td> - <div class="story"> - <h6 class="kicker"><a href="http://www.nytimes.com/opinion">Opinion &raquo;</a></h6> - <h3><a href="http://www.nytimes.com/2010/07/25/opinion/25schlosser.html">Eric Schlosser: Unsafe at Any Meal</a></h3> - <p class="summary">Will the Senate act to protect Americans from food-borne illnesses?</p> - </div> - </td> - <td> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/weekinreview/index.html">Week in Review &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/weekinreview/25burns.html"><img src="http://graphics8.nytimes.com/images/2010/07/25/weekinreview/25moth_burns/25moth_burns-moth.jpg" alt="The Vagabond Cat That Came to Stay" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/weekinreview/25burns.html">The Vagabond Cat That Came to Stay</a></h6> - </div> - </td> - <td> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/travel/index.html">Travel &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://travel.nytimes.com/2010/07/25/travel/25Tokaj.html"><img src="http://graphics8.nytimes.com/images/2010/07/25/travel/25moth_tokaj/25moth_tokaj-moth.jpg" alt="Hidden in Hungary, Treasures on the Vine" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://travel.nytimes.com/2010/07/25/travel/25Tokaj.html">Hidden in Hungary, Treasures on the Vine</a></h6> - </div> - </td> - <td class="hidden"> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/magazine/index.html">Magazine &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/magazine"><span class="img" src="http://graphics8.nytimes.com/images/2010/07/25/magazine/25moth-magcov/25moth-magcov-moth.jpg" alt="The Web Means the End of Forgetting" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/magazine">The Web Means the End of Forgetting</a></h6> - </div> - </td> - <td class="hidden"> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/opinion">Opinion &raquo;</a> </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/opinion/25hartley.html"><span class="img" src="http://graphics8.nytimes.com/images/2010/07/25/opinion/25moth_oped1/25moth_oped1-moth.jpg" alt="Op-Ed: Tea With a Terrorist" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/opinion/25hartley.html">Op-Ed: Tea With a Terrorist</a></h6> - </div> - </td> - <td class="hidden"> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/nyregion/index.html">N.Y. / Region &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/nyregion/25oneblock.html"><span class="img" src="http://graphics8.nytimes.com/images/2010/07/25/nyregion/25moth-oneblock/25moth-oneblock-moth.jpg" alt="The Stories of One Brooklyn Block" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/nyregion/25oneblock.html">The Stories of One Brooklyn Block</a></h6> - </div> - </td> - <td class="hidden"> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/arts/music/index.html">Music &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/arts/music/25feminism.html"><span class="img" src="http://graphics8.nytimes.com/images/2010/07/25/arts/music/25moth_feminism/25moth_feminism-moth.jpg" alt="Girl Pop&rsquo;s Lady Gaga Makeover" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/arts/music/25feminism.html">Girl Pop&rsquo;s Lady Gaga Makeover</a></h6> - </div> - </td> - <td class="hidden"> - <div class="story"> - <h6 class="kicker"><a href="http://www.nytimes.com/opinion">Opinion &raquo;</a></h6> - <h3><a href="http://www.nytimes.com/2010/07/25/opinion/25jacobsen.html">Op-Ed: Where Oysters Grew on Trees</a></h3> - <p class="summary">The Gulf Coast was once the nation&rsquo;s gold mine of marine life. The BP spill gives us a chance to restore it.</p> - </div> - </td> - <td class="hidden"> - <div class="story"> - <h6 class="kicker"> - <a href="http://www.nytimes.com/pages/books/index.html">Books &raquo;</a> - </h6> - <div class="mothImage"> - <a href="http://www.nytimes.com/2010/07/25/books/review/Mishra-t.html"><span class="img" src="http://graphics8.nytimes.com/images/2010/07/25/books/25moth_mishra/25moth_mishra-moth.jpg" alt="Yoga in America" width="151" height="151" /></a> - </div> - <h6 class="headline"><a href="http://www.nytimes.com/2010/07/25/books/review/Mishra-t.html">Yoga in America</a></h6> - </div> - </td> - </tr> - </tbody> - </table> - </div> - - </div><!-- end #insideNYTimes --> - - </div><!--close main --> -<div id="footer"> -<ul class="first"> -<li class="first"><a href="http://www.nytimes.com">Home</a></li> -<li > -<a href="http://www.nytimes.com/pages/world/index.html">World</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/national/index.html">U.S.</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/nyregion/index.html">N.Y. / Region</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/business/index.html">Business</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/technology/index.html">Technology</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/science/index.html">Science</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/health/index.html">Health</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/sports/index.html">Sports</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/opinion/index.html">Opinion</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/arts/index.html">Arts</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/style/index.html">Style</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/travel/index.html">Travel</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/jobs/index.html">Jobs</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/realestate/index.html">Real Estate</a> -</li> -<li > -<a href="http://www.nytimes.com/pages/automobiles/index.html">Autos</a> -</li> -<li><a href="#top">Back to Top</a></li> -</ul> <ul> -<li class="first"><a href="http://www.nytimes.com/ref/membercenter/help/copyright.html">Copyright 2010</a> <a href="http://www.nytco.com/">The New York Times Company</a></li> -<li><a href="http://www.nytimes.com/privacy">Privacy</a></li> -<li><a href="http://www.nytimes.com/ref/membercenter/help/agree.html">Terms of Service</a></li> -<li><a href="http://www.nytimes.com/search">Search</a></li> -<li><a href="http://www.nytimes.com/corrections.html">Corrections</a></li> -<li><a class="rssButton" href="http://www.nytimes.com/rss">RSS</a></li> -<li><a href="http://firstlook.nytimes.com">First Look</a></li> -<li><a href="http://www.nytimes.com/membercenter/sitehelp.html">Help</a></li> -<li><a href="http://www.nytimes.com/ref/membercenter/help/infoservdirectory.html">Contact Us</a></li> -<li><a href="https://careers.nytco.com/TAM/nyt_docs/TAM/candidate.html">Work for Us</a></li> -<li><a href="http://www.nytimes.whsites.net/mediakit/">Advertise</a></li> -<li><a href="http://spiderbites.nytimes.com/">Site Map</a></li> -</ul> -</div> -</div><!--close page --> -</div><!--close shell --> -<IMG SRC="/adx/bin/clientside/6bc2e5a3Q2FGQ20Q60nQ25eGcjQ3EekQ24j3Q7EQ20ceQ5EQ22Q7ECQ25Q5EQ25Q5EhQ60h_shl_Q3CQ25Cha" height="1" width="3"> - - - -</body> - - - - <!-- Start UPT call --> - <img height="1" width="3" border=0 src="http://up.nytimes.com/?d=0/4/&t=2&s=0&ui=0&r=http%3a%2f%2fwww%2enytimes%2ecom%2f&u=www%2enytimes%2ecom%2f2010%2f07%2f26%2fbusiness%2fglobal%2f26bp%2ehtml%3fhp%3d"> - <!-- End UPT call --> - - - <script language="JavaScript"><!-- - var dcsvid="0"; - var regstatus="non-registered"; - //--></script> - <script src="http://graphics8.nytimes.com/js/app/analytics/trackingTags_v1.1.js" type="text/javascript"></script> - <noscript> - <div><img alt="DCSIMG" id="DCSIMG" width="1" height="1" src="http://wt.o.nytimes.com/dcsym57yw10000s1s8g0boozt_9t1x/njs.gif?dcsuri=/nojavascript&amp;WT.js=No&amp;WT.tv=1.0.7"/></div> - </noscript> - -<!-- ADXINFO classification="blank-but-count-imps" campaign="Inv-Test_ROS-Country"--><img src="http://graphics8.nytimes.com/ads/blank.gif"> - -<script src="http://content.dl-rms.com/rms/25887/nodetag.js"></script> -</html> - - - - - diff --git a/src/test/resources/htmltests/nyt-article-1.html.gz b/src/test/resources/htmltests/nyt-article-1.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..3b50b7230f37f061d8c0ff32ec1e235e79d16f38 GIT binary patch literal 15694 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 A+W-In literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/smh-biz-article-1.html b/src/test/resources/htmltests/smh-biz-article-1.html deleted file mode 100644 index cb2cdf0173..0000000000 --- a/src/test/resources/htmltests/smh-biz-article-1.html +++ /dev/null @@ -1,1633 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> -<head> - <title>The board’s next fear: the female quota</title> - <meta http-equiv="content-language" content="en" /> -<meta http-equiv="imagetoolbar" content="no" /> -<meta name="robots" content="noarchive,noodp" /> -<meta name="robots" content="ACAP allow-index" /> -<meta name="robots" content="ACAP allow-follow" /> -<meta name="robots" content="ACAP disallow-preserve" /> -<meta name="robots" content="ACAP allow-present prohibited-modification=annotation" /> -<meta name="description" content="" /> -<meta name="keywords" content="" /> - - - -<!-- Styles =================================================================== --> -<!-- Have these styles for both screen and print to create a base for printing: --> -<link rel="stylesheet" type="text/css" media="screen,print" href="http://resources.smh.com.au/common/media-common-1.0/css/base.css" /> -<!-- Skin CSS: --> -<link rel="stylesheet" type="text/css" media="screen" href="http://resources.smh.com.au/common/media-common-1.0/css/base-skin-news/skin-news.css" /> -<link rel="stylesheet" type="text/css" media="screen" href="http://resources.smh.com.au/common/media-common-1.0/css/skin-business/skin-business.css" /> - <link rel="stylesheet" type="text/css" media="screen" href="http://resources.smh.com.au/common/media-common-1.0/css/skin-promotions/skin-promotions.css" /> -<link rel="alternate stylesheet" type="text/css" media="screen" title="High contrast" href="http://resources.smh.com.au/common/media-common-1.0/css/base-skin-news/skin-news-low-vision.css" /> -<!-- Any adjustments we need to do for handheld devices: --> -<link rel="stylesheet" type="text/css" media="handheld" href="http://resources.smh.com.au/common/media-common-1.0/css/mobile.css" /> - -<script type="text/javascript" > - var delayedAds = []; -</script> - - - - <!-- All Javascript calls go here, after the content so loading and parsing a script does not affect content loading: --> - <script type="text/javascript" src="http://resources.smh.com.au/common/media-common-1.0/js/fd.mt.media.com.au.js"></script> -<script type="text/javascript" src="http://resources.smh.com.au/common/media-common-1.0/js/fd.media.custom.js"></script> -<script type="text/javascript" src="http://resources.smh.com.au/common/media-common-1.0/js/adParams.js"></script> -<script type="text/javascript" src="http://resources.smh.com.au/common/media-common-1.0/js/europa.lite.packed.js"></script> - -<script type="text/javascript"> - - function initPreAdLoadImage() { - new Element("img", { - width: 1, - height: 1, - src: "http://direct.fairfax.com.au/vserver/CCID=1/AREA=BUSINESS.SMH.BUSINESS.COLUMNIST/BT=1/ACC_RANDOM=678700", - styles: { - display: 'none' - } - }); - } - - FD.register("PreAdLoadImage"); - - var siteAdvertDesc = { - redir: "/adredirect.html?ad=" - }; - - - FD.baseAd = { - src: "http://direct.fairfax.com.au/jserver/", - params: { - cat: "BUSINESS", - site: "ONL.MH.SMH.BUSINESS", - ctype: "ARTICLE", - area: "BUSINESS.SMH.BUSINESS.COLUMNIST", - cat1: "COLUMNIST", - isiframe: "yes" - } - }; - - function initPost() { - - - try { - document.domain = "smh.com.au"; - } - catch (e) - { - // if working dev the above will fail - } - - for(var i = 0; i < delayedAds.length; i++) { - delayedAds[i](); - } - } -</script> - - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> - -<body class="smh business"> -<script type="text/javascript"> -// <![CDATA[ - document.body.className += " scriptable"; -// ]]> -</script> - - -<a class="skipLink" href="#nav">Skip to navigation</a> -<a class="skipLink" href="#content">Skip to content</a> - -<div class="wrap cfix"> - - <div id="adspot-1x10" class=""></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-1x10", - iframeId: "adspot-1x10-iframe", - params: $merge($merge(FD.baseAd.params, { - aamsz : "1x10" - }),getAdParams("1x10")) - - ,height: 1 - ,width: 1 - }) - ); - } -); - -</script> - - -<!-- Network strip top (scope: network-wide) --> -<div class="nN-whiteStrip"> - <a class="logo" href="http://www.fairfaxdigital.com.au/" onclick="linktop(this);">Fairfax Digital</a> - <ul class="links"> - <li><a href="http://newsbreak.com.au/" onclick="linktop(this);">News</a></li> - <li><a href="http://mycareer.com.au/" onclick="linktop(this);">MyCareer</a></li> - <li><a href="http://www.domain.com.au/" onclick="linktop(this);">Domain</a></li> - <li><a href="http://www.drive.com.au/" onclick="linktop(this);">Drive</a></li> - <li><a href="http://businessday.com.au/" onclick="linktop(this);">Finance</a></li> - <li><a href="http://mobile.fairfax.com.au/" onclick="linktop(this);">Mobile</a></li> - <li><a href="http://www.rsvp.com.au/" onclick="linktop(this);">RSVP</a></li> - <li><a href="http://www.smh.com.au/travel/travel-landing/" onclick="linktop(this);">Travel</a></li> - <li class="last"><a href="http://www.weatherzone.com.au/" onclick="linktop(this);">Weather</a></li> - </ul> - <ul class="memberCentre"> - <li><a href="http://www.fairfax.com.au/map/" onclick="linktop(this);">network map</a></li><li class="last"><a href="http://fairfaxdigital.com.au/" onclick="linktop(this);">member centre</a></li> - </ul> -</div> -<!-- End component: Network strip top --> - - -<!-- Header (class according to new HTML5 element 'header'; was 'masthead') (scope: network-wide) --> -<div class="header span-24"> - <div id="adspot-468X60-pos-1" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-468X60-pos-1", - iframeId: "adspot-468X60-pos-1-iframe", - params: $merge($merge(FD.baseAd.params, { - pos: 1, - adtype: 'panorama', - aamsz : "468X60" - }),getAdParams("468X60")) - - }) - ); - } -); - -</script> - <p class="mh-logo"><a href="http://www.smh.com.au" title="The Sydney Morning Herald">The Sydney Morning Herald</a></p> - <h2><a href="http://business.smh.com.au" title="Business">Business</a></h2> - - <!-- Navigation (scope: network-wide) --> - <ul id="nav" class="hasSubNav"> - - - - - <li class="selected"> - <a href="http://www.smh.com.au/business" title="News">News</a> - - <ol> - <li> - <a href="http://www.smh.com.au/business/national" title="Today's News & Views">Today's News & Views</a> - </li> - <li> - <a href="http://www.smh.com.au/business/opinion" title="Comment & Analysis">Comment & Analysis</a> - </li> - <li> - <a href="http://www.smh.com.au/business/world" title="World Business">World Business</a> - </li> - <li> - <a href="http://www.smh.com.au/business/marketing" title="Media & Marketing">Media & Marketing</a> - </li> - <li> - <a href="http://www.smh.com.au/business/print-edition" title="Newspaper Edition">Newspaper Edition</a> - </li> - <li class="last"> - <a href="http://mobile.fairfax.com.au/" title="BusinessDay Mobile">BusinessDay Mobile</a> - </li> - </ol> - - - </li> - - - - - - - <li> - <a href="http://www.smh.com.au/business/markets" title="Markets">Markets</a> - - - - </li> - - - - - - - <li> - <a href="http://markets.smh.com.au/apps/qt/index.ac" title="Quotes">Quotes</a> - - - - </li> - - - - - - - <li> - <a href="http://markets.smh.com.au/apps/pf/index.ac" title="Portfolio">Portfolio</a> - - - - </li> - - - - - - - <li> - <a href="http://www.smh.com.au/business/money/" title="Money">Money</a> - - - - </li> - - - - - - - <li> - <a href="http://www.smh.com.au/business/property" title="Property Focus">Property Focus</a> - - - - </li> - - - - - - - <li class=" hasDropdown startup"> - <a href="http://smallbusiness.smh.com.au/" title="Small Business">Small Business</a> - - - <ul> - <li class="first"> - <a href="http://www.smh.com.au/small-business/startup" title="Startup">Startup</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/managing" title="Managing">Managing</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/franchising" title="Franchising">Franchising</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/trends" title="Trends">Trends</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/entrepreneur" title="Entrepreneur">Entrepreneur</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/marketing" title="Marketing">Marketing</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/finance" title="Finance">Finance</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/technology" title="Technology">Technology</a> - </li> - <li> - <a href="http://www.smh.com.au/small-business/resources" title="Resources">Resources</a> - </li> - <li class="last"> - <a href="http://www.smh.com.au/small-business/coaching" title="Coaching">Coaching</a> - </li> - </ul> - - </li> - - - - - - - <li class=" hasDropdown "> - <a href="http://www.smh.com.au/business/executivestyle/" title="Executive Style">Executive Style</a> - - - <ul> - <li class="first"> - <a href="http://www.smh.com.au/executive-style/travel/" title="Travel">Travel</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/motors/" title="Motors">Motors</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/culture/" title="Culture">Culture</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/gadgets" title="Gadgets">Gadgets</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/luxury" title="Luxury">Luxury</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/management/" title="Management">Management</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/style/" title="Style">Style</a> - </li> - <li> - <a href="http://www.smh.com.au/executive-style/top-drop" title="Wine">Wine</a> - </li> - <li class="last"> - <a href="http://www.smh.com.au/executive-style/fitness" title="Fitness">Fitness</a> - </li> - </ul> - - </li> - - - - - - - <li class=" hasDropdown "> - <a href="#" title="Compare & Save">Compare & Save</a> - - - <ul> - <li class="first"> - <a href="http://compare.smh.com.au/credit-cards" title="Credit Cards">Credit Cards</a> - </li> - <li> - <a href="http://compare.smh.com.au/debit-cards" title="Debit Cards">Debit Cards</a> - </li> - <li> - <a href="http://compare.smh.com.au/home-loans" title="Home Loans">Home Loans</a> - </li> - <li> - <a href="http://compare.smh.com.au/personal-loans" title="Personal Loans">Personal Loans</a> - </li> - <li> - <a href="http://compare.smh.com.au/car-loans" title="Car Loans">Car Loans</a> - </li> - <li> - <a href="http://compare.smh.com.au/term-deposits" title="Term Deposits">Term Deposits</a> - </li> - <li> - <a href="http://compare.smh.com.au/bank-accounts" title="Bank Accounts">Bank Accounts</a> - </li> - <li class="last"> - <a href="http://compare.smh.com.au/savings-accounts" title="Saving Accounts">Saving Accounts</a> - </li> - </ul> - - </li> - - - - - - - <li class=" sponsor-1"> - <a href="http://www.smh.com.au/national/cfdeducation" title="Trader Insights">Trader Insights</a> - - - - </li> - - - </ul> - <!-- End module Navigation --> - - - <!-- Breadcrumb (scope: network-wide) --> - <p class="breadcrumb"> - <span>You are here:</span> - <a href="http://www.smh.com.au">Home</a> - &raquo; <a href="http://www.smh.com.au/business">BusinessDay</a> - &raquo; <a href="http://www.smh.com.au/business/by/Michael-Pascoe">Michael Pascoe</a> - &raquo; <a href="http://www.smh.com.au/business/the-boards-next-fear-the-female-quota-20100106-lteq.html">Article</a> - </p> - - - <ul class="altFormats"> - <li class="mobiles"> - <a href="http://mobile.fairfax.com.au/">Mobiles</a> - </li> - <li class="rss"> - <a href="http://www.smh.com.au/rssheadlines">RSS</a> - </li> - <li class="newsletters"> - <a href="https://membercentre.fairfax.com.au/NewsletterSubscription.aspx">Newsletters</a> - </li> - <li id="vision" class="last"><a href="#">High contrast</a></li> - </ul> - - - <!-- Search (scope:network-wide) --> - <div class="cN-searchBox cfix"> - <form action="http://www.smh.com.au/execute_search.html" method="get"> - <h2>Search smh:</h2> - <label for="search"> - <input type="text" name="text" id="search" value='Search here...' onfocus="this.value='';" /> - </label> - <h3>Search in:</h3> - <ul id="ddown" class="ddown"> - <li><a class="selected" href="#">Business</a> - <div class="srch-wrap"> - <div></div> - <ul class="cfix"> - <li class="first"><a href="#">smh.com.au</a></li> - <li><a href="#">Web</a></li> - <li><a href="#">Business</a></li> - </ul> - </div> - </li> - </ul> - <input type="hidden" name="ss" value="Business" /> - <input type="submit" class="btnSubmit" value="Search" /> - </form> - </div> - - -</div> -<!-- End component: Header --> - - - - <div id="adspot-940X20-pos-1" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-940X20-pos-1", - iframeId: "adspot-940X20-pos-1-iframe", - params: $merge($merge(FD.baseAd.params, { - pos: 1, - adtype: 'panorama', - aamsz : "940X20" - }),getAdParams("940X20")) - - ,height: 1 - ,width: 940 - }) - ); - } -); - -</script> - - <!-- Content. We use an id here to be able to jump to this section. --> - <div id="content" class="span-16"> - - <!-- cN-headingPage --> - <h1 class="cN-headingPage prepend-5 span-11 last"><HEADLINE>The board’s next fear: the female quota</HEADLINE></h1> - - <!-- Class 'push-0' just right-aligns the element so that the main content comes first. --> - <div class="push-0 span-11 last"> - - -<!-- cT-storyDetails --> -<div class="cT-storyDetails cfix"> - <cite>January 6, 2010 - 1:12PM</cite> -</div> - -<div class="ad adSpot-textBox" id="googleAds"></div> - -<BOD> - <div class="articleBody"> - <p>For all the protestations, there’s been an almost audible sigh of relief in the directors’ club about the watered-down and more workable recommendations from the Productivity Commission on remuneration report votes. Besides, the whole question of obscene executive pay was effectively ducked anyway.</p> - <p>But the way is now cleared for various boardrooms to move on to the next big fear: that the Federal Government might impose a quota for female directors. After all, the Labor Party has done something like that to itself with a quota of 40 per cent of candidates being women, resulting in a third of Labor MHRs being female.</p> - <p>(In case you’re wondering, women have 27 per cent of all House of Reps seats and make up 36 per cent of the Senate.)</p> - <p>In a recent social setting and therefore anonymously, a prominent member of the directors’ club and chairman of major company told me his fellows were appalled at the prospect.</p> - <p>The usual reservations were cited: a quota means the best person for the job may not be chosen; it demeans women who are chosen as the suspicion will be that they didn’t get the job on merit.</p> - <p>But much more important to this chairman was that there was not a sufficient pool of female talent at the top of the executive aquatic centre from which to fish an imposed quota of directors. And once a government started mandating quotas for one group, he feared it would be liable to repeat the exercise for another. A quota for indigenous Australians, perhaps.</p> - <p>My suspicion is that this chairman was representative of the thinking of his peers, if not what they’re prepared to say in public. In that case, there’s a lot more to be done in the evolution of women CEOs before the boardrooms can start to mirror Federal Parliament – and it shouldn’t need to be said that there is a vast difference between what’s required to be a “successful” MP and a successful director, as the miserable boardroom careers of many retired pollies indicates.</p> - <p>This week’s Economist magazine cover revives the World War II image of Rosie the Riveter with the headline: “We Did It! What happens when women are over half the workforce”. It celebrates the fact that in the next few months, women will cross the 50 per cent threshold and make up the majority of the American workforce.</p> - <p>“Women already make up the majority of university graduates in the OECD countries and the majority of professional workers in several rich countries, including the United States. Women run many of the world’s great companies, from PepsiCo in America to Areva in France,” notes the Economist’s leader – and it could have continued “and Westpac and Harvey Norman in Australia”.</p> - <p>“Women’s economic empowerment is arguably the biggest social change of our times. Just a generation ago, women were largely confined to repetitive, menial jobs. They were routinely subjected to casual sexism and were expected to abandon their careers when they married and had children. Today they are running some of the organisations that once treated them as second-class citizens. Millions of women have been given more control over their own lives. And millions of brains have been put to more productive use. Societies that try to resist this trend - most notably the Arab countries, but also Japan and some southern European countries - will pay a heavy price in the form of wasted talent and frustrated citizens.”</p> - <p>The leader goes on to face the two major remaining issues: that women are still under-represented at the top making up only 2 per cent of the bosses of major American companies and 5 per cent of British; and that women are paid significantly less than men on average.</p> - <p>In keeping with the Economist’s rational and liberal credo, it argues for, mostly, letting the market do the work in fixing those problems as it’s been doing a good job of it over the past decade. When the Scandinavian experience is cited as a way of speeding up the process, the magazine answers:</p> - <p>“If that means massive intervention, in the shape of affirmative-action programs and across-the-board benefits for parents of all sorts, the answer is no. To begin with, promoting people on the basis of their sex is illiberal and unfair, and stigmatises its beneficiaries. And there are practical problems. Lengthy periods of paid maternity leave can put firms off hiring women, which helps explain why most Swedish women work in the public sector and Sweden has a lower proportion of women in management than America does.”</p> - <p>The leader argues that there are plenty of cheaper and subtler ways in which governments can make life easier for women.</p> - <p>“Welfare states were designed when most women stayed at home. They need to change the way they operate. German schools, for instance, close at midday. American schools shut down for two months in the summer. These things can be changed without huge cost.”</p> - <p>Perhaps without huge cost, but it would be a very brave minister who would take on the Teachers’ Union for a start. It would be so much easier for a federal Labor politicians to simply mandate a female quota for the nation’s boardrooms, but as the saying goes: For every large and difficult problem, you can generally find an easy solution – and it will be wrong.</p> - <p><span style="font-style: italic;">Michael Pascoe is a BusinessDay contributing editor.</span></p> - </div> - <!-- articleBody --> -</BOD> - - - - <div class="ad adSpot-textBox" id="moreGoogleAds"></div> - - <div align="right"> - <a href="http://direct.fairfax.com.au/adclick/CID=000215760000000000000000 "><b>To advertise your business on Google...click here</b></a> - </div> - - <!-- cT-comments --> -<div id="comments" class="cT-comments"> - <a id="makeComment" name="makeComment"></a> - - -</div> -<!-- cT-comments --> - - - <div class="adSpot-textBoxGraphicRight cfix"> - <div id="adspot-300x20-pos-1" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-300x20-pos-1", - iframeId: "adspot-300x20-pos-1-iframe", - src:[ - FD.baseAd.src + 'POS=1/', - FD.baseAd.src + 'POS=2/' - ], - params: $merge($merge(FD.baseAd.params, { - aamsz : "300x20" - }),getAdParams("300x20")) - - }) - ); - } -); - -</script> - <div id="adspot-300x20-pos-2" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-300x20-pos-2", - iframeId: "adspot-300x20-pos-2-iframe", - src:[ - FD.baseAd.src + 'POS=3/', - FD.baseAd.src + 'POS=4/' - ], - params: $merge($merge(FD.baseAd.params, { - aamsz : "300x20" - }),getAdParams("300x20")) - - }) - ); - } -); - -</script> -</div> - - - </div><!-- inner content area --> - - - <div class="sidebar span-5"> - <div class="cT-headshot"> - <div> - <a href="http://www.smh.com.au/business/by/Michael-Pascoe" title="Michael Pascoe"><img src="http://images.smh.com.au/2009/10/24/810836/michael-pascoe_127x127-90x90.jpg" width="90" height="90" alt="michael-pascoe_127x127" /></a> - - <h3><a href="http://www.smh.com.au/business/by/Michael-Pascoe" title="Michael Pascoe">Michael Pascoe</a></h3> - </div> - <p> - - <a href="http://www.smh.com.au/business/by/Michael-Pascoe"> - More Michael Pascoe articles - </a> - </p> - </div> - - <div class="cT-socialCommenting accessibleSocialComment"> - - - <h3>Join the conversation</h3> - - <p class="people"><em>2</em> people are reading this now.</p> - <p class="twitter"><a href="http://twitter.com/home?status=%23fdlteq http://smh.com.au/business-lteq.html" target="_blank">Comment on Twitter</a>.<br /> - <a href="http://search.twitter.com/search?q=%23fdlteq" target="_blank"><em>Read tweets</em></a>.</p> - -</div> - - - - - - - - - - <div class="cN-linkList"> - <h3>Top Business articles</h3> - <ol> - <li class="first"> - <a href="http://www.smh.com.au/business/westpac-rate-rise-pushes-customers-to-switch-banks-20100105-lsbd.html" title="Westpac rate rise 'pushes customers to switch banks'" style="">Westpac rate rise 'pushes customers to switch banks'</a> - </li> - <li> - <a href="http://www.smh.com.au/business/a-strikes-twoyear-high-against-the-euro-20100105-ls79.html" title="$A strikes two-year high against the euro" style="">$A strikes two-year high against the euro</a> - </li> - <li> - <a href="http://www.smh.com.au/business/red-hot-property-sector-points-to-rate-rise-20100106-ltb6.html" title="'Red hot' property sector points to rate rise" style="">'Red hot' property sector points to rate rise</a> - </li> - <li> - <a href="http://www.smh.com.au/business/jetstar-and-airasia-in-lowcost-alliance-20100106-lsze.html" title="Jetstar and AirAsia in low-cost alliance" style="">Jetstar and AirAsia in low-cost alliance</a> - </li> - <li> - <a href="http://www.smh.com.au/business/rip-out-vineyards-lehmann-20100105-ls76.html" title="Rip out vineyards: Lehmann" style="">Rip out vineyards: Lehmann</a> - </li> - <li class="last"><a href="http://www.smh.com.au/business">More Business articles</a></li> - </ol> - </div> - - - <div class="cN-topicSelector"> - <h3>Business Topics</h3> - <div class="cN-groupNavigator open"> - <h4><a href="#">Companies<span>Expand</span> (4618)</a></h4> - <ul> - <li class="all"><a href="/business/Companies">All Companies</a> (4618)</li> - <li><a href="/business/Companies/AMP">AMP</a> (827)</li> - <li><a href="/business/Companies/Westpac">Westpac</a> (567)</li> - <li><a href="/business/Companies/ANZ">ANZ</a> (544)</li> - <li><a href="/business/Companies/Commonwealth-Bank">Commonwealth Bank</a> (498)</li> - <li><a href="/business/Companies/BHP-Billiton">BHP Billiton</a> (425)</li> - <li class="hide"><a href="/business/Companies/Rio-Tinto">Rio Tinto</a> (417)</li> - <li class="hide"><a href="/business/Companies/NAB">NAB</a> (344)</li> - <li class="hide"><a href="/business/Companies/Telstra">Telstra</a> (343)</li> - <li class="hide"><a href="/business/Companies/Seek">Seek</a> (299)</li> - <li class="hide"><a href="/business/Companies/Woolworths">Woolworths</a> (255)</li> - <li class="hide"><a href="/business/Companies/Qantas">Qantas</a> (223)</li> - <li class="hide"><a href="/business/Companies/Wesfarmers">Wesfarmers</a> (183)</li> - <li class="hide"><a href="/business/Companies/David-Jones">David Jones</a> (156)</li> - <li class="hide"><a href="/business/Companies/AXA">AXA</a> (149)</li> - <li class="hide"><a href="/business/Companies/Woodside-Petroleum">Woodside Petroleum</a> (141)</li> - <li class="hide"><a href="/business/Companies/Santos">Santos</a> (128)</li> - <li class="hide"><a href="/business/Companies/ING">ING</a> (124)</li> - <li class="hide"><a href="/business/Companies/Fortescue-Metals">Fortescue Metals</a> (105)</li> - <li class="hide"><a href="/business/Companies/Fairfax-Media">Fairfax Media</a> (102)</li> - <li class="hide"><a href="/business/Companies/Oil-Search">Oil Search</a> (96)</li> - <li class="hide"><a href="/business/Companies/Origin-Energy">Origin Energy</a> (96)</li> - <li class="hide"><a href="/business/Companies/Macquarie-Airports">Macquarie Airports</a> (91)</li> - <li class="hide"><a href="/business/Companies/Leighton">Leighton</a> (91)</li> - <li class="hide"><a href="/business/Companies/Crown">Crown</a> (87)</li> - <li class="hide"><a href="/business/Companies/AWB">AWB</a> (87)</li> - <li class="hide"><a href="/business/Companies/Centro">Centro</a> (82)</li> - <li class="hide"><a href="/business/Companies/Lend-Lease">Lend Lease</a> (81)</li> - <li class="hide"><a href="/business/Companies/ERA">ERA</a> (80)</li> - <li class="hide"><a href="/business/Companies/Perpetual">Perpetual</a> (75)</li> - <li class="hide"><a href="/business/Companies/Seven-Network">Seven Network</a> (74)</li> - <li class="hide"><a href="/business/Companies/Virgin-Blue">Virgin Blue</a> (73)</li> - <li class="hide"><a href="/business/Companies/Bank-of-Queensland">Bank of Queensland</a> (71)</li> - <li class="hide"><a href="/business/Companies/Harvey-Norman">Harvey Norman</a> (69)</li> - <li class="hide"><a href="/business/Companies/Westfield">Westfield</a> (68)</li> - <li class="hide"><a href="/business/Companies/OZ-Minerals">OZ Minerals</a> (66)</li> - <li class="hide"><a href="/business/Companies/Bunnings">Bunnings</a> (64)</li> - <li class="hide"><a href="/business/Companies/Transurban">Transurban</a> (62)</li> - <li class="hide"><a href="/business/Companies/AGL">AGL</a> (61)</li> - <li class="hide"><a href="/business/Companies/Macquarie-Bank">Macquarie Bank</a> (61)</li> - <li class="hide"><a href="/business/Companies/Asciano">Asciano</a> (58)</li> - <li class="hide"><a href="/business/Companies/Foster-s">Foster's</a> (56)</li> - <li class="hide"><a href="/business/Companies/Lihir-Gold">Lihir Gold</a> (55)</li> - <li class="hide"><a href="/business/Companies/Newcrest-Mining">Newcrest Mining</a> (55)</li> - <li class="hide"><a href="/business/Companies/Great-Southern">Great Southern</a> (54)</li> - <li class="hide"><a href="/business/Companies/Mirvac">Mirvac</a> (52)</li> - <li class="hide"><a href="/business/Companies/Consolidated-Media-Holdings">Consolidated Media Holdings</a> (51)</li> - <li class="hide"><a href="/business/Companies/Goodman">Goodman</a> (50)</li> - <li class="hide"><a href="/business/Companies/Suncorp-Metway">Suncorp-Metway</a> (50)</li> - <li class="hide"><a href="/business/Companies/Tabcorp">Tabcorp</a> (49)</li> - <li class="hide"><a href="/business/Companies/ABC-Learning">ABC Learning</a> (49)</li> - <li class="hide"><a href="/business/Companies/Timbercorp">Timbercorp</a> (48)</li> - <li class="hide"><a href="/business/Companies/BankWest">BankWest</a> (48)</li> - <li class="hide"><a href="/business/Companies/CSR">CSR</a> (47)</li> - <li class="hide"><a href="/business/Companies/Ten-Network">Ten Network</a> (46)</li> - <li class="hide"><a href="/business/Companies/Gunns">Gunns</a> (45)</li> - <li class="hide"><a href="/business/Companies/JB-Hi-Fi">JB Hi-Fi</a> (44)</li> - <li class="hide"><a href="/business/Companies/Elders">Elders</a> (44)</li> - <li class="hide"><a href="/business/Companies/Stockland">Stockland</a> (44)</li> - <li class="hide"><a href="/business/Companies/James-Hardie">James Hardie</a> (43)</li> - <li class="hide"><a href="/business/Companies/GPT">GPT</a> (42)</li> - <li class="hide"><a href="/business/Companies/Amcor">Amcor</a> (41)</li> - <li class="hide"><a href="/business/Companies/Nufarm">Nufarm</a> (41)</li> - <li class="hide"><a href="/business/Companies/Felix-Resources">Felix Resources</a> (40)</li> - <li class="hide"><a href="/business/Companies/Lion-Nathan">Lion Nathan</a> (39)</li> - <li class="hide"><a href="/business/Companies/Metcash">Metcash</a> (39)</li> - <li class="hide"><a href="/business/Companies/Toll-Holdings">Toll Holdings</a> (37)</li> - <li class="hide"><a href="/business/Companies/Caltex">Caltex</a> (36)</li> - <li class="hide"><a href="/business/Companies/Brambles">Brambles</a> (35)</li> - <li class="more"><a href="#">More Companies</a></li> - </ul> - </div> - <div class="cN-groupNavigator close"> - <h4><a href="#">People<span>Expand</span> (2513)</a></h4> - <ul> - <li class="all"><a href="/business/People">All People</a> (2513)</li> - <li><a href="/business/People/James-Packer">James Packer</a> (107)</li> - <li><a href="/business/People/Rupert-Murdoch">Rupert Murdoch</a> (86)</li> - <li><a href="/business/People/Mike-Smith">Mike Smith</a> (73)</li> - <li><a href="/business/People/Ralph-Norris">Ralph Norris</a> (69)</li> - <li><a href="/business/People/Cameron-Clyne">Cameron Clyne</a> (68)</li> - <li class="hide"><a href="/business/People/Gail-Kelly">Gail Kelly</a> (64)</li> - <li class="hide"><a href="/business/People/Kerry-Stokes">Kerry Stokes</a> (61)</li> - <li class="hide"><a href="/business/People/David-Thodey">David Thodey</a> (58)</li> - <li class="hide"><a href="/business/People/Marius-Kloppers">Marius Kloppers</a> (47)</li> - <li class="hide"><a href="/business/People/Sol-Trujillo">Sol Trujillo</a> (46)</li> - <li class="hide"><a href="/business/People/Lachlan-Murdoch">Lachlan Murdoch</a> (46)</li> - <li class="hide"><a href="/business/People/Andrew-Forrest">Andrew Forrest</a> (41)</li> - <li class="hide"><a href="/business/People/Alan-Joyce">Alan Joyce</a> (38)</li> - <li class="hide"><a href="/business/People/Craig-Dunn">Craig Dunn</a> (38)</li> - <li class="hide"><a href="/business/People/Don-Argus">Don Argus</a> (35)</li> - <li class="more"><a href="#">More People</a></li> - </ul> - </div> - <div class="cN-groupNavigator close"> - <h4><a href="#">Topics<span>Expand</span> (1784)</a></h4> - <ul> - <li class="all"><a href="/business/Topics">All Topics</a> (1784)</li> - <li><a href="/business/Topics/Interest-Rates">Interest Rates</a> (827)</li> - <li><a href="/business/Topics/Global-Financial-Crisis">Global Financial Crisis</a> (739)</li> - <li><a href="/business/Topics/Superannuation">Superannuation</a> (221)</li> - <li><a href="/business/Topics/Pension">Pension</a> (152)</li> - <li><a href="/business/Topics/Stimulus-Package">Stimulus Package</a> (124)</li> - <li class="hide"><a href="/business/Topics/Budget-Deficit">Budget Deficit</a> (55)</li> - <li class="more"><a href="#">More Topics</a></li> - </ul> - </div> - <div class="cN-groupNavigator close"> - <h4><a href="#">Organisations<span>Expand</span> (1611)</a></h4> - <ul> - <li class="all"><a href="/business/Organisations">All Organisations</a> (1611)</li> - <li><a href="/business/Organisations/Reserve-Bank">Reserve Bank</a> (846)</li> - <li><a href="/business/Organisations/ASX">ASX</a> (365)</li> - <li><a href="/business/Organisations/ASIC">ASIC</a> (266)</li> - <li><a href="/business/Organisations/ACCC">ACCC</a> (122)</li> - <li><a href="/business/Organisations/APRA">APRA</a> (52)</li> - </ul> - </div> -</div> - - - - - - -<!-- cT-storyTools --> -<div class="cT-storyTools cfix"> - <!-- cT-strapHeading --> - <h3 class="cT-strapHeading">Story Tools</h3> - - <ul class="accessibleStoryTools"> - <li class="facebook"><a href="http://www.facebook.com/share.php?u=http://www.smh.com.au/business/the-boards-next-fear-the-female-quota-20100106-lteq.html" onclick="javascript:u='http://www.smh.com.au/business/the-boards-next-fear-the-female-quota-20100106-lteq.html';t='The board’s next fear: the female quota';window.open('http://www.facebook.com/sharer.php?u='+encodeURIComponent(u)+'&t='+encodeURIComponent(t),'sharer','toolbar=0,status=0,width=626,height=436');return false;" rel="nofollow">Share on Facebook</a></li> - <li class="email"><a href="/action/emailToFriend?id=1017890" onclick="var popup =window.open('/action/emailToFriend?id=1017890','EmailArticle','toolbar=no,menubar=no,width=760,height=680,resizable=yes,menubar=no,status=no,scrollbars=no');popup.focus();return false" title="Email to a friend" rel="nofollow">Email this story</a></li> - <li class="print"><a href="/action/printArticle?id=1017890" onclick="var popup =window.open('/action/printArticle?id=1017890','PrintArticle','toolbar=no,menubar=no,width=1024,height=620,resizable=yes,menubar=no,status=no,scrollbars=yes');popup.focus();return false" title="Print this story" rel="nofollow">Print this story</a></li> - </ul> - - <div id="adspot-180x44" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-180x44", - iframeId: "adspot-180x44-iframe", - params: $merge($merge(FD.baseAd.params, { - aamsz : "180x44" - }),getAdParams("180x44")) - - ,height: 75 - ,width: 180 - }) - ); - } -); - -</script> -</div> -<!-- cT-storyTools --> - - <div class="cT-storyTools cfix"> - <h3 class="cT-strapHeading">SMH Jobs</h3> - <iframe width="180" height="400" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" src="http://commercial.adperfect.com/adserving/dynamicwebads/adframe.php?AdTagID=C0A801C31d59123256lhqjCAAA14&ap_params=&r=444525"></iframe> -</div> - - </div><!-- left sidebar --> - - - <div class="span-16 last"> - - - - - - - - - - </div> - - - </div><!-- content --> - - <!-- Aside (class according to new HTML5 element 'aside'; was 'sidebar') --> - <div class="aside span-8 last"> - - <div id="adspot-300x250-pos-1" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-300x250-pos-1", - iframeId: "adspot-300x250-pos-1-iframe", - params: $merge($merge(FD.baseAd.params, { - pos: 1, - adtype: 'doubleisland', - aamsz : "300x250" - }),getAdParams("300x250")) - - ,addSmall: true - }) - ); - } -); - -</script> - - <div class="sstrap executivestyle cfix cS-sectionPromo"> - - <h2 class="cfix"><a href="http://smh.com.au/executive-style/">Executive Style</a><span class="inlineRight"><div id="adSpot-lastop"></div></span></h2> - <ul class="inlineRight"> - <li><a href="http://smh.com.au/executive-style/business-travel/">Travel</a></li> - <li><a href="http://smh.com.au/executive-style/motors/">Motors</a></li> - <li><a href="http://smh.com.au/executive-style/culture/">Culture</a></li> - <li><a href="http://smh.com.au/executive-style/gadgets/">Gadgets</a></li> - <li><a href="http://smh.com.au/executive-style/management">Management</a></li> - <li class="last"><a href="http://smh.com.au/executive-style/style">Style</a></li> - </ul> - <!--@List: Executive Style Puff --> - <!--@Asset: 1015488 --> - <div class="puff"> - <a href="http://www.smh.com.au/executive-style/fitness/adventurer-aims-for-the-north-pole-20100105-lrk0.html"> - <img src="http://images.smh.com.au/2010/01/05/1015720/rhs300-smitheringale-300x0.jpg" alt="Tom Smitheringale."/> - </a> - <div class="caption"> - <h3><a href="http://www.smh.com.au/executive-style/fitness/adventurer-aims-for-the-north-pole-20100105-lrk0.html" title="Adventurer aims for the North Pole">Adventurer aims for the North Pole</a></h3> - <p>The risk of a frozen death does not phase Tom Smitheringale. He will trek to the North Pole, alone.</p> - </div> - </div> - <!--@RelateImage--> - <!--/@Asset--> - <!--@Asset: 1014788 --> - <div class="wof"> - <h3><a href="http://www.smh.com.au/executive-style/fitness/sleep-loss-may-affect-health-by-curbing-exercise-20100105-lr0k.html"><strong>Physical activity:</strong> </a> <a href="http://www.smh.com.au/executive-style/fitness/sleep-loss-may-affect-health-by-curbing-exercise-20100105-lr0k.html" title="Impact of sleep loss on health">Impact of sleep loss on health</a></h3> - <p> - <a href="http://www.smh.com.au/executive-style/fitness/sleep-loss-may-affect-health-by-curbing-exercise-20100105-lr0k.html"> - <img src="http://images.smh.com.au/2009/09/08/719064/thumb140_gym-90x60.jpg" alt="A gym." /> - </a> - Over time, a lack of sleep could affect a person's weight and general health because of the impact is has on physical activity, study shows. - </p> - </div> - <!--@RelateImage--> - <!--/@Asset--> - <!--@Asset: 1010931 --> - <div class="wof"> - <h3><a href="http://www.smh.com.au/executive-style/culture/when-pinball-was-king-20100104-lo1f.html"><strong>Arcade action:</strong> </a> <a href="http://www.smh.com.au/executive-style/culture/when-pinball-was-king-20100104-lo1f.html" title="When pinball was king">When pinball was king</a></h3> - <p> - <a href="http://www.smh.com.au/executive-style/culture/when-pinball-was-king-20100104-lo1f.html"> - <img src="http://images.smh.com.au/2010/01/04/1013000/thumb140_pinball-90x60.jpg" alt="One of Tony Mather's pinball machines." /> - </a> - It was outlawed in the '40s and killed off by Space Invaders in the '80s. But for a few glorious decades pinball ruled. - </p> - </div> - <!--@RelateImage--> - <!--/@Asset--> - <!--@Asset: 731361 --> - <div class="wof"> - <h3><a href="http://blogs.theage.com.au/executive-style/allmenareliars/"><strong>All Men are Liars:</strong> </a> <a href="http://blogs.theage.com.au/executive-style/allmenareliars/" title="Pardon the self promotion">Pardon the self promotion</a></h3> - <p> - <a href="http://blogs.theage.com.au/executive-style/allmenareliars/"> - <img src="http://images.smh.com.au/2009/11/11/851134/thumb140_samdebrito_headshot-index-90x60.jpg" alt="Sam De Brito, All Men are Liars blog." /> - </a> - Nominate All Men Are Liars for the 2010 Weblog Awards. - </p> - </div> - <!--@RelateImage--> - <!--/@Asset--> - <!--@Asset: 1005318 --> - <div class="wof"> - <h3><a href="http://www.smh.com.au/photogallery/executive-style/luxury/worlds-top-ski-destinations/20091230-ljpi.html"><strong>Snow blind:</strong> </a> <a href="http://www.smh.com.au/photogallery/executive-style/luxury/worlds-top-ski-destinations/20091230-ljpi.html" title="World's top ski destinations">World's top ski destinations</a></h3> - <p> - <a href="http://www.smh.com.au/photogallery/executive-style/luxury/worlds-top-ski-destinations/20091230-ljpi.html"> - <img src="http://images.smh.com.au/2009/12/31/1006160/th_skiwhistler3-90x60.jpg" alt="skiing" /> - </a> - For die hard powder-hounds and devoted apres-skiers alike, these resorts are the best in snow business. - </p> - </div> - <!--@RelateImage--> - <!--/@Asset--> - <!--/@List--> -</div> - - - <div id="adspot-149x170" class="ad adSpot-twin"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-149x170", - iframeId: "adspot-149x170-iframe", - src:[ - FD.baseAd.src + 'POS=1/', - FD.baseAd.src + 'POS=2/' - ], - params: $merge($merge(FD.baseAd.params, { - aamsz : "149x170" - }),getAdParams("149x170")) - - ,addSmall: "top" - ,smallText: "Featured advertisers" - }) - ); - } -); - -</script> - - <?xml version="1.0" encoding="UTF-8"?><h3 class="cN-headerRich bankRates">Today's bank rates<span class="ad"><a href="http://mozo.com.au/">Powered by Mozo</a></span></h3><ul class="cN-tabBox cS-bankRates accessibleTab" id="cN-tabBox-mozo"><li class="tab1 selected"><h4><a rel="nofollow" href="http://mozo.com.au/activity/record_widget_link/3/savings%20accounts?tabName=Savings Accounts#" class="cN-externalTarget">Savings Accounts</a></h4><div><p><span>Percentages denote the </span>interest rate</p><dl><dt><strong>UBank</strong>USaver</dt><dd><em>5.51%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=127&amp;product_type=SavingsAccount&amp;provider_id=172&amp;path=/feed/fairfax_widget_03&amp;position=1&amp;the_url=adsfac.net/link.asp%3fcc=UBA006.100897.0%26clk=1%26creativeID=147817%26ord=[timestamp]&amp;partial=fairfax_widget_03&amp;tab_name=savings accounts&amp;alternatives=[104, 38, 27]&amp;tabName=Savings Accounts" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>ANZ</strong>Online Saver</dt><dd><em>5.25%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=104&amp;product_type=SavingsAccount&amp;provider_id=7&amp;path=/feed/fairfax_widget_03&amp;position=2&amp;the_url=ad.au.doubleclick.net/clk;214149089;36030226;z&amp;partial=fairfax_widget_03&amp;tab_name=savings accounts&amp;alternatives=[127, 38, 27]&amp;tabName=Savings Accounts" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>ING DIRECT</strong>Savings Maximiser</dt><dd><em>4.25%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=38&amp;product_type=SavingsAccount&amp;provider_id=65&amp;path=/feed/fairfax_widget_03&amp;position=3&amp;the_url=clk.atdmt.com/MOS/go/178423674/direct/01/&amp;partial=fairfax_widget_03&amp;tab_name=savings accounts&amp;alternatives=[127, 104, 27]&amp;tabName=Savings Accounts" target="_blank" rel="nofollow">Get info</a></dd></dl><dl class="last"><dt><strong>Suncorp</strong>eOptions</dt><dd><em>4.00%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=27&amp;product_type=SavingsAccount&amp;provider_id=115&amp;path=/feed/fairfax_widget_03&amp;position=4&amp;the_url=www.s2d6.com/x/%3fx=c%26z=s%26v=1276499%26k=[NETWORKID] &amp;partial=fairfax_widget_03&amp;tab_name=savings accounts&amp;alternatives=[127, 104, 38]&amp;tabName=Savings Accounts" target="_blank" rel="nofollow">Get info</a></dd></dl><a class="more" target="_blank" href="/savings-accounts">Compare all savings accounts</a></div></li><li class="tab2"><h4><a rel="nofollow" href="http://mozo.com.au/activity/record_widget_link/3/term%20deposits?tabName=Term Deposits#" class="cN-externalTarget">Term Deposits</a></h4><div><p><span>Percentages denote the </span>interest rate</p><dl><dt><strong>ING DIRECT</strong> 1 year annual</dt><dd><em>6.10%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=148&amp;product_type=TermDeposit&amp;provider_id=65&amp;path=/feed/fairfax_widget_03&amp;position=1&amp;the_url=clk.atdmt.com/MOS/go/184647488/direct/01/&amp;partial=fairfax_widget_03&amp;tab_name=term deposits&amp;alternatives=[336, 311, 243]&amp;tabName=Term Deposits" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>Citibank</strong> 1 year annual</dt><dd><em>6.00%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=336&amp;product_type=TermDeposit&amp;provider_id=29&amp;path=/feed/fairfax_widget_03&amp;position=2&amp;the_url=www.citibank.com.au/td&amp;partial=fairfax_widget_03&amp;tab_name=term deposits&amp;alternatives=[148, 311, 243]&amp;tabName=Term Deposits" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>Laiki Bank</strong> 180 days</dt><dd><em>5.70%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=311&amp;product_type=TermDeposit&amp;provider_id=167&amp;path=/feed/fairfax_widget_03&amp;position=3&amp;the_url=www.laiki.com/web/w3au.nsf/WebPromotionDocsByID/ID-7564BA120DAB256BCA2574FA0019BDFC&amp;partial=fairfax_widget_03&amp;tab_name=term deposits&amp;alternatives=[148, 336, 243]&amp;tabName=Term Deposits" target="_blank" rel="nofollow">Get info</a></dd></dl><dl class="last"><dt><strong>Macquarie</strong> 90 days</dt><dd><em>5.50%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=243&amp;product_type=TermDeposit&amp;provider_id=71&amp;path=/feed/fairfax_widget_03&amp;position=4&amp;the_url=members.commissionmonster.com/z/87402/11571/&amp;partial=fairfax_widget_03&amp;tab_name=term deposits&amp;alternatives=[148, 336, 311]&amp;tabName=Term Deposits" target="_blank" rel="nofollow">Get info</a></dd></dl><a class="more" target="_blank" href="/term-deposits">Compare all term deposits</a></div></li><li class="tab3"><h4><a rel="nofollow" href="http://mozo.com.au/activity/record_widget_link/3/credit%20cards?tabName=Credit Cards#" class="cN-externalTarget">Credit Cards</a></h4><div><p><span>Percentages denote the </span>interest rate</p><dl><dt><strong>BankWest</strong>Lite</dt><dd><em>9.99%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=66&amp;product_type=CreditCard&amp;provider_id=19&amp;path=/feed/fairfax_widget_03&amp;position=1&amp;the_url=clk.atdmt.com/IKS/go/194280364/direct/01/&amp;partial=fairfax_widget_03&amp;tab_name=credit cards&amp;alternatives=[22, 67, 89]&amp;tabName=Credit Cards" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>NAB</strong>Low Rate</dt><dd><em>11.74%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=22&amp;product_type=CreditCard&amp;provider_id=85&amp;path=/feed/fairfax_widget_03&amp;position=2&amp;the_url=clk.atdmt.com/NOZ/go/121418006/direct/01/&amp;partial=fairfax_widget_03&amp;tab_name=credit cards&amp;alternatives=[66, 67, 89]&amp;tabName=Credit Cards" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>ANZ</strong>Low Rate</dt><dd><em>12.49%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=67&amp;product_type=CreditCard&amp;provider_id=7&amp;path=/feed/fairfax_widget_03&amp;position=3&amp;the_url=ad.au.doubleclick.net/clk;219508103;42723872;d&amp;partial=fairfax_widget_03&amp;tab_name=credit cards&amp;alternatives=[66, 22, 89]&amp;tabName=Credit Cards" target="_blank" rel="nofollow">Get info</a></dd></dl><dl class="last"><dt><strong>Citibank</strong>Platinum Card</dt><dd><em>20.49%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=89&amp;product_type=CreditCard&amp;provider_id=29&amp;path=/feed/fairfax_widget_03&amp;position=4&amp;the_url=www.citibank.com.au/cardsoffer/0909PlatFlyFree.htm%3fCode=P1C79HYQ&amp;partial=fairfax_widget_03&amp;tab_name=credit cards&amp;alternatives=[66, 22, 67]&amp;tabName=Credit Cards" target="_blank" rel="nofollow">Get info</a></dd></dl><a class="more" target="_blank" href="/credit-cards">Compare all credit cards</a></div></li><li class="tab4"><h4><a rel="nofollow" href="http://mozo.com.au/activity/record_widget_link/3/home%20loans?tabName=Home Loans#" class="cN-externalTarget">Home Loans</a></h4><div><p><span>Percentages denote the </span>comparison rate</p><dl><dt><strong>MyRate.com.au</strong>Advantage Loan </dt><dd><em>5.84%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=289&amp;product_type=HomeLoan&amp;provider_id=83&amp;path=/feed/fairfax_widget_03&amp;position=1&amp;the_url=www.myrate.com.au/save%3fa_id=mozo_listing&amp;partial=fairfax_widget_03&amp;tab_name=home loans&amp;alternatives=[372, 379, 170]&amp;tabName=Home Loans" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>Laiki Bank</strong>Options Plus</dt><dd><em>6.00%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=372&amp;product_type=HomeLoan&amp;provider_id=167&amp;path=/feed/fairfax_widget_03&amp;position=2&amp;the_url=www.laiki.com/web/w3au.nsf/WebPromotionDocsByID/ID-F3FBE78446C1B0BFCA2573330023C634&amp;partial=fairfax_widget_03&amp;tab_name=home loans&amp;alternatives=[289, 379, 170]&amp;tabName=Home Loans" target="_blank" rel="nofollow">Get info</a></dd></dl><dl><dt><strong>Aussie</strong>Classic Line of Credit</dt><dd><em>6.52%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=379&amp;product_type=HomeLoan&amp;provider_id=10&amp;path=/feed/fairfax_widget_03&amp;position=3&amp;the_url=clk.atdmt.com/AUS/go/188865657/direct/01/ &amp;partial=fairfax_widget_03&amp;tab_name=home loans&amp;alternatives=[289, 372, 170]&amp;tabName=Home Loans" target="_blank" rel="nofollow">Get info</a></dd></dl><dl class="last"><dt><strong>Aussie</strong>Standard Fixed</dt><dd><em>6.77%</em><span/></dd><dd class="last"><a class="applyParamsForExternal" href="#http://mozo.com.au/activity/record_gts2?product_id=170&amp;product_type=HomeLoan&amp;provider_id=10&amp;path=/feed/fairfax_widget_03&amp;position=4&amp;the_url=clk.atdmt.com/AUS/go/188865657/direct/01/ &amp;partial=fairfax_widget_03&amp;tab_name=home loans&amp;alternatives=[289, 372, 379]&amp;tabName=Home Loans" target="_blank" rel="nofollow">Get info</a></dd></dl><a class="more" target="_blank" href="/home-loans">Compare all home loans</a></div></li></ul> - - <div id="adspot-295x60" class="ad "></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-295x60", - iframeId: "adspot-295x60-iframe", - src:[ - FD.baseAd.src + 'POS=1/', - FD.baseAd.src + 'POS=2/', - FD.baseAd.src + 'POS=3/' - ], - params: $merge($merge(FD.baseAd.params, { - aamsz : "295x60" - }),getAdParams("295x60")) - - ,addSmall: "top" - ,smallText: "Sponsored links" - }) - ); - } -); - -</script> - - <div id="adspot-1x4" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-1x4", - iframeId: "adspot-1x4-iframe", - params: $merge($merge(FD.baseAd.params, { - aamsz : "1x4" - }),getAdParams("1x4")) - - ,height: 150 - ,width: 300 - ,addSmall: true - }) - ); - } -); - -</script> - - <div id="adspot-300x250-pos-2" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-300x250-pos-2", - iframeId: "adspot-300x250-pos-2-iframe", - params: $merge($merge(FD.baseAd.params, { - pos: 2, - aamsz : "300x250" - }),getAdParams("300x250")) - - ,addSmall: true - }) - ); - } -); - -</script> - - - - - </div> - - <!-- Footer fN-footerNetwork (class according to new HTML5 element 'footer'; scope: network-wide) --> -<div class="footer span-24"> - <script type="text/javascript"> -function trackOmnitureClick(anchor,identifier) { - var hash = ""; - var hashIndex = anchor.href.indexOf("#"); - if (hashIndex > -1) { - hash = anchor.href.substring(hashIndex); - anchor.href = anchor.href.substring(0, hashIndex); - } - if(anchor.href.indexOf("s_rid") > -1) { - // Remove any s_rid's - anchor.href = anchor.href.replace(/[\??|&?]s_rid=(.*)/i, ""); - } - if(anchor.href.indexOf("s_cid") > -1) { - // Strip any s_cid's - anchor.href = anchor.href.replace(/[\??|&?]s_cid=(.*)/i, ""); - } - var query = (anchor.href.indexOf("?") > -1) ? "&" : "?"; - anchor.href += query + "s_rid="+identifier; - if (hashIndex > -1) { - anchor.href += hash; - } - return true; -} -</script> - - -<div class="c5 classifieds cfix"> - - <div class="s1 cBusinessDay"> - <h2><a title="Business" onclick="trackOmnitureClick(this,'smh:rainbowstrip:label:fatcats_172x115');" href="http://www.businessday.com.au">Business</a></h2> - <div class="puff"> - <a href="http://www.smh.com.au/business/executive-pay-given-a-reprieve-20100103-lnas.html" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content1:04-01:fatcats_172x115');" ><img src="http://images.smh.com.au/2010/01/04/1010993/fatcats_172x115-172x115.jpg" width="172" alt="Fat cats"/></a> - - <h5><a href="http://www.smh.com.au/business/executive-pay-given-a-reprieve-20100103-lnas.html" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content2:04-01:fatcats_172x115:executivepaygivenareprieve');" title="Executive pay given a reprieve">Executive pay given a reprieve</a></h5> - </div> - <ul> - <li class="lBusinessDay"><a href="http://www.businessday.com.au" onclick="trackOmnitureClick(this,'smh:rainbowstrip:logo:04-01:fatcats_172x115');">Businessday.com.au</a></li> - <li><a href="http://www.smh.com.au/business/" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet1:04-01:fatcats_172x115:latestbusinessnews');">Latest Business news</a></li> - <li><a href="http://www.smh.com.au/executive-style/" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet2:04-01:fatcats_172x115:executivestyle');">Executive Style</a></li> - <li><a href="http://www.smh.com.au/small-business" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet3:04-01:fatcats_172x115:mysmallbusiness');">MySmallBusiness</a></li> - </ul> - </div> - <div class="s2 cBusinessDay"> - <h2><a title="Business" onclick="trackOmnitureClick(this,'smh:rainbowstrip:label:nab_172x115');" href="http://www.businessday.com.au">Business</a></h2> - <div class="puff"> - <a href="http://www.smh.com.au/business/nab-set-to-pounce-on-northern-rock-20100104-lnxs.html" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content1:04-01:nab_172x115');" ><img src="http://images.smh.com.au/2010/01/04/1012429/nab_172x115-172x115.jpg" width="172" alt="NAB"/></a> - - <h5><a href="http://www.smh.com.au/business/nab-set-to-pounce-on-northern-rock-20100104-lnxs.html" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content2:04-01:nab_172x115:nabsettopounceonnorthernrock');" title="NAB set to pounce on Northern Rock">NAB set to pounce on Northern Rock</a></h5> - </div> - <ul> - <li class="lBusinessDay"><a href="http://www.businessday.com.au" onclick="trackOmnitureClick(this,'smh:rainbowstrip:logo:04-01:nab_172x115');">Businessday.com.au</a></li> - <li><a href="http://www.smh.com.au/business/markets" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet1:04-01:nab_172x115:marketreports');">Market Reports</a></li> - <li><a href="http://www.smh.com.au/business/money/" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet2:04-01:nab_172x115:moneynews');">Money news</a></li> - <li><a href="http://www.smh.com.au/business/property" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet3:04-01:nab_172x115:propertyfocus');">Property Focus</a></li> - </ul> - </div> - <div class="s3 cSmh"> - <h2><a title="Video" onclick="trackOmnitureClick(this,'smh:rainbowstrip:label:dustin');" href="http://www.smh.com.au">Video</a></h2> - <div class="puff"> - <a href="http://media.fairfax.com.au/?rid=56862" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content1:05-01:dustin');" ><img src="http://images.smh.com.au/2010/01/05/1015399/dustin-172x115.jpg" width="172" alt="Dustin Hoffman"/></a> - - <h5><a href="http://media.fairfax.com.au/?rid=56862" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content2:05-01:dustin:dustinhoffmansitaliantourismad');" title="Dustin Hoffman's Italian tourism ad">Dustin Hoffman's Italian tourism ad</a></h5> - </div> - <ul> - <li class="lSmh"><a href="http://www.smh.com.au" onclick="trackOmnitureClick(this,'smh:rainbowstrip:logo:05-01:dustin');">Smh.com.au</a></li> - <li><a href="http://media.smh.com.au/lifestyle/essentials" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet1:07-12:dustin:life&stylevideos');">Life & Style videos</a></li> - <li><a href="http://media.smh.com.au/entertainment/red-carpet" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet2:07-12:dustin:entertainmentvideos');">Entertainment videos</a></li> - <li><a href="http://media.smh.com.au/business/businessday" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet3:07-12:dustin:businessvideos');">Business videos</a></li> - </ul> - </div> - <div class="s4 cInvestSmart"> - <h2><a title="InvestSMART" onclick="trackOmnitureClick(this,'smh:rainbowstrip:label:2009lights');" href="http://www.investsmart.com.au">InvestSMART</a></h2> - <div class="puff"> - <a href="http://www.investsmart.com.au/managed-funds/top-managed-funds.asp?s_cid=promostrip:top2009" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content1:05-01:2009lights');" ><img src="http://images.smh.com.au/2010/01/05/1015432/2009lights-172x115.gif" width="172" alt="2009"/></a> - - <h5><a href="http://www.investsmart.com.au/managed-funds/top-managed-funds.asp?s_cid=promostrip:top2009" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content2:05-01:2009lights:topperforminginvestmentfundsattheendof2009');" title="Top Performing Investment Funds at the end of 2009">Top Performing Investment Funds at the end of 2009</a></h5> - </div> - <ul> - <li class="lInvestSmart"><a href="http://www.investsmart.com.au" onclick="trackOmnitureClick(this,'smh:rainbowstrip:logo:05-01:2009lights');">Investsmart.com.au</a></li> - <li><a href="http://www.investsmart.com.au/share_trading/open_account.asp" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet1:23-10:2009lights:openanaccount');">Open an account</a></li> - <li><a href="http://www.investsmart.com.au/managed-funds/top-managed-funds.asp" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet2:23-10:2009lights:topperformingmanagedfunds');">Top Performing Managed Funds</a></li> - <li><a href="http://www.investsmart.com.au/share_trading/open_account.asp" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet3:23-10:2009lights:sharetrading');">Share Trading</a></li> - </ul> - </div> - <div class="s5 cSmh"> - <h2><a title="Sport" onclick="trackOmnitureClick(this,'smh:rainbowstrip:label:jwrainbow3');" href="http://www.smh.com.au">Sport</a></h2> - <div class="puff"> - <a href="http://pages.email.fairfax.com.au/smh/jwsummerofcricket?s_cid=rainbow:smh:johnniewalkercricket:dec09:feb10" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content1:04-01:jwrainbow3');" ><img src="http://images.smh.com.au/2010/01/04/1012523/JWRainbow3-172x115.jpg" width="172" alt="WIN"/></a> - - <h5><a href="http://pages.email.fairfax.com.au/smh/jwsummerofcricket?s_cid=rainbow:smh:johnniewalkercricket:dec09:feb10" rel="nofollow" onclick="trackOmnitureClick(this,'smh:rainbowstrip:content2:04-01:jwrainbow3:winweeklyjohnniewalkerprizes');" title="Win weekly Johnnie Walker prizes">Win weekly Johnnie Walker prizes</a></h5> - </div> - <ul> - <li class="lSmh"><a href="http://www.smh.com.au" onclick="trackOmnitureClick(this,'smh:rainbowstrip:logo:04-01:jwrainbow3');">Smh.com.au</a></li> - <li><a href="http://www.smh.com.au/sport/cricket" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet1:21-12:jwrainbow3:cricketnews');">Cricket news</a></li> - <li><a href="http://www.smh.com.au/sport/cricket" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet2:21-12:jwrainbow3:livecricketscores');">Live cricket scores</a></li> - <li><a href="http://www.smh.com.au/sport/" onclick="trackOmnitureClick(this,'smh:rainbowstrip:bullet3:21-12:jwrainbow3:latestinsport');">Latest in Sport</a></li> - </ul> - </div> - -</div> - - - - <div class="c5 affStrip"> - <h2>Compare and Save</h2> - <ul> - <li ><a href="http://broadband.smh.com.au/Broadband/" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:link')">Broadband</a></li> - <li ><a href="http://smh.goswitch.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:link')">Energy</a></li> - <li ><a href="http://mobile-phones.smh.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:link')">Mobile</a></li> - <li ><a href="http://compare.smh.com.au/home-loans" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:link')">Home Loans</a></li> - <li class="last"><a href="http://compare.smh.com.au/savings-accounts" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:link')">Savings Accounts</a></li> -</ul> - - <span></span> - <div> - <h3><a href="http://compare.smh.com.au/term-deposits" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content0')">Grab what you can!</a></h3> - <a href="http://compare.smh.com.au/term-deposits" rel="nofollow" onclick="trackOmnitureClick(this,'smh:affiliatestrip:content0:td---grab 5 jan');" ><img src="http://images.smh.com.au/2010/01/05/1014762/TD---Grab 5 Jan-60x90.jpg" width="60" alt="TD---Grab 5 Jan"/></a> - - <p><a href="http://compare.smh.com.au/term-deposits" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content0')">A show of hands for the best term deposit.</a></p> - <p class="links"><a href="http://compare.smh.com.au/term-deposits" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content0')">Compare all Term Deposits</a></p> - </div> - <div> - <h3><a href="http://compare.smh.com.au/savings-accounts" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content1')">Gimme gimme gimme!</a></h3> - <a href="http://compare.smh.com.au/savings-accounts" rel="nofollow" onclick="trackOmnitureClick(this,'smh:affiliatestrip:content1:cc---gold-diggers 21 dec');" ><img src="http://images.smh.com.au/2009/12/21/991026/CC---Gold-Diggers 21 Dec-60x90.jpg" width="60" alt="CC---Gold-Diggers 21 Dec"/></a> - - <p><a href="http://compare.smh.com.au/savings-accounts" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content1')">Earn 5.51% on your savings</a></p> - <p class="links"><a href="http://compare.smh.com.au/savings-accounts" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content1')">Compare all Savings Accounts</a></p> - </div> - <div> - <h3><a href="http://thecellar.thecorridor.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content2')">Discounted Wine @ The Corridor</a></h3> - <a href="http://thecellar.thecorridor.com.au/" rel="nofollow" onclick="trackOmnitureClick(this,'smh:affiliatestrip:content2:ishop 14 dec');" ><img src="http://images.smh.com.au/2009/12/14/968217/iShop 14 Dec-60x90.jpg" width="60" alt="iShop 14 Dec"/></a> - - <p><a href="http://thecellar.thecorridor.com.au/" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content2')">Up to 25% off all off our wine @ The Corridor. Over 35 well known brands on sale</a></p> - <p class="links"><a href="http://thecellar.thecorridor.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content2')">Compare our prices today</a></p> - </div> - <div> - <h3><a href="http://mobile-phones.smh.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content3')">Mobile Phone Plans</a></h3> - <a href="http://mobile-phones.smh.com.au" rel="nofollow" onclick="trackOmnitureClick(this,'smh:affiliatestrip:content3:mobile_60x90_phone 18 dec');" ><img src="http://images.smh.com.au/2009/12/18/984193/mobile_60x90_phone 18 Dec-60x90.gif" width="60" alt="mobile_60x90_phone 18 Dec"/></a> - - <p><a href="http://mobile-phones.smh.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content3')">Compare iPhones and Mobile Plans for Christmas.</a></p> - <p class="links"><a href="http://mobile-phones.smh.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content3')">Compare Mobile Phones & Plans</a></p> - </div> - <div class="last"> - <h3><a href="http://broadband.smh.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content4')">Broadband Plans</a></h3> - <a href="http://broadband.smh.com.au/Broadband/" rel="nofollow" onclick="trackOmnitureClick(this,'smh:affiliatestrip:content4:bband_60x90_laptop 18 dec');" ><img src="http://images.smh.com.au/2009/12/18/984311/bband_60x90_laptop 18 Dec-60x90.gif" width="60" alt="bband_60x90_laptop 18 Dec"/></a> - - <p><a href="http://broadband.smh.com.au/Broadband/" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content4')">Broadband Plans to cope with your Christmas downloads</a></p> - <p class="links"><a href="http://broadband.smh.com.au" onclick="trackOmnitureClick(this, 'smh:affiliatestrip:content4')">Compare all Broadband Plans</a></p> - </div> - -</div> - - -<!-- Reader's most viewed --> -<div class="c5 top5 cfix"> - <h2 class="cfix">Readers' most viewed</h2> - - <div class="s1 lBT"> - <a href="http://www.brisbanetimes.com.au">Most viewed articles on Brisbane Times</a> - <h5>Top 5 <a href="http://www.brisbanetimes.com.au/business">Business</a> articles</h5> - <ol> - <li><a href="http://www.brisbanetimes.com.au/business/queenslands-cheapest-beachfront-address-20100106-ltfu.html" title="Queensland's cheapest beachfront address " style="">Queensland's cheapest beachfront address </a></li> - <li><a href="http://www.brisbanetimes.com.au/business/queenslands-shonky-business-capital-revealed-20100106-lsqd.html" title="Queensland's shonky business capital revealed" style="">Queensland's shonky business capital revealed</a></li> - <li><a href="http://www.brisbanetimes.com.au/business/westpac-rate-rise-pushes-customers-to-switch-banks-20100105-lsbd.html" title="Westpac rate rise 'pushes customers to switch banks'" style="">Westpac rate rise 'pushes customers to switch banks'</a></li> - <li><a href="http://www.brisbanetimes.com.au/business/jetstar-and-airasia-in-lowcost-alliance-20100106-lsze.html" title="Jetstar and AirAsia in low-cost alliance" style="">Jetstar and AirAsia in low-cost alliance</a></li> - <li><a href="http://www.brisbanetimes.com.au/business/red-hot-property-sector-points-to-rate-rise-20100106-ltb6.html" title="'Red hot' property sector points to rate rise" style="">'Red hot' property sector points to rate rise</a></li> - </ol> - - </div> - <div class="s2 lWAToday"> - <a href="http://www.watoday.com.au">Most viewed articles on WA Today</a> - <h5>Top 5 <a href="http://www.watoday.com.au/business">Business</a> articles</h5> - <ol> - <li><a href="http://www.watoday.com.au/business/westpac-customers-walk-over-rate-rise-20100105-lscq.html" title="Westpac customers walk over rate rise" style="">Westpac customers walk over rate rise</a></li> - <li><a href="http://www.watoday.com.au/business/a-strikes-twoyear-high-against-the-euro-20100105-ls79.html" title="$A strikes two-year high against the euro" style="">$A strikes two-year high against the euro</a></li> - <li><a href="http://www.watoday.com.au/business/rip-out-vines-wine-industry-told-20100105-lsdj.html" title="Rip out vines, wine industry told" style="">Rip out vines, wine industry told</a></li> - <li><a href="http://www.watoday.com.au/business/webjet-is-just-the-ticket-20100105-lsdu.html" title="Webjet is just the ticket" style="">Webjet is just the ticket</a></li> - <li><a href="http://www.watoday.com.au/business/red-hot-property-sector-points-to-rate-rise-20100106-ltb6.html" title="'Red hot' property sector points to rate rise" style="">'Red hot' property sector points to rate rise</a></li> - </ol> - - </div> - <div class="s3 lAge"> - <a href="http://www.theage.com.au">Most viewed articles on The Age</a> - <h5>Top 5 <a href="http://www.theage.com.au/business">Business</a> articles</h5> - <ol> - <li><a href="http://www.theage.com.au/business/westpac-customers-walk-over-rate-rise-20100105-lscq.html" title="Westpac customers walk over rate rise" style="">Westpac customers walk over rate rise</a></li> - <li><a href="http://www.theage.com.au/business/a-strikes-twoyear-high-against-the-euro-20100105-ls79.html" title="$A strikes two-year high against the euro" style="">$A strikes two-year high against the euro</a></li> - <li><a href="http://www.theage.com.au/business/rip-out-vines-wine-industry-told-20100105-lsdj.html" title="Rip out vines, wine industry told" style="">Rip out vines, wine industry told</a></li> - <li><a href="http://www.theage.com.au/business/red-hot-property-sector-points-to-rate-rise-20100106-ltb6.html" title="'Red hot' property sector points to rate rise" style="">'Red hot' property sector points to rate rise</a></li> - <li><a href="http://www.theage.com.au/business/markets/shares-flat-as-miners-offset-bank-falls-20100106-lsod.html" title="Shares flat as miners offset bank falls" style="">Shares flat as miners offset bank falls</a></li> - </ol> - - </div> - <div class="s4 lSmh"> - <a href="http://www.smh.com.au">Most viewed articles on The Sydney Morning Herald</a> - <h5>Top 5 <a href="http://www.smh.com.au/business">Business</a> articles</h5> - <ol> - <li><a href="http://www.smh.com.au/business/westpac-rate-rise-pushes-customers-to-switch-banks-20100105-lsbd.html" title="Westpac rate rise 'pushes customers to switch banks'" style="">Westpac rate rise 'pushes customers to switch banks'</a></li> - <li><a href="http://www.smh.com.au/business/a-strikes-twoyear-high-against-the-euro-20100105-ls79.html" title="$A strikes two-year high against the euro" style="">$A strikes two-year high against the euro</a></li> - <li><a href="http://www.smh.com.au/business/red-hot-property-sector-points-to-rate-rise-20100106-ltb6.html" title="'Red hot' property sector points to rate rise" style="">'Red hot' property sector points to rate rise</a></li> - <li><a href="http://www.smh.com.au/business/jetstar-and-airasia-in-lowcost-alliance-20100106-lsze.html" title="Jetstar and AirAsia in low-cost alliance" style="">Jetstar and AirAsia in low-cost alliance</a></li> - <li><a href="http://www.smh.com.au/business/rip-out-vineyards-lehmann-20100105-ls76.html" title="Rip out vineyards: Lehmann" style="">Rip out vineyards: Lehmann</a></li> - </ol> - - </div> - <div class="s5"> - <h5>Videos</h5> - <ol> - <li><a href="http://media.smh.com.au/sport/sports-hq/rooney-key-for-man-united-1017709.html" title="Rooney key for Man United" style="">Rooney key for Man United</a></li> - <li><a href="http://media.smh.com.au/entertainment/red-carpet/nude-hawkins-cover-defended-1012683.html" title="Nude Hawkins cover defended" style="">Nude Hawkins cover defended</a></li> - <li><a href="http://media.smh.com.au/watson-chokes-on-a-ton-again-1015990.html" title="Watson chokes on a ton again" style="">Watson chokes on a ton again</a></li> - <li><a href="http://media.smh.com.au/world/world-news/uk-endures-deep-freeze-1016948.html" title="UK endures deep freeze" style="">UK endures deep freeze</a></li> - <li><a href="http://media.smh.com.au/national/national-news/1911-antarctic-plane-recovered-1017703.html" title="1911 Antarctic plane recovered" style="">1911 Antarctic plane recovered</a></li> - </ol> - - </div> -</div> - - <div id="adspot-468x60-pos-2" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-468x60-pos-2", - iframeId: "adspot-468x60-pos-2-iframe", - params: $merge($merge(FD.baseAd.params, { - pos: 2, - adtype: 'panorama', - aamsz : "468x60" - }),getAdParams("468x60")) - - }) - ); - } -); - -</script> - <!-- Footer section links --> - <ul class="fSectionLinks cfix"> - <li ><a href="http://www.smh.com.au/" title="SMH Home">SMH Home</a></li> - <li ><a href="http://www.smh.com.au/news/national/" title="National">National</a></li> - <li ><a href="http://www.smh.com.au/news/world/" title="World">World</a></li> - <li ><a href="http://www.smh.com.au/opinion" title="Opinion">Opinion</a></li> - <li ><a href="http://www.smh.com.au/business/" title="Business">Business</a></li> - <li ><a href="http://www.smh.com.au/technology/" title="Technology">Technology</a></li> - <li ><a href="http://www.smh.com.au/sport/" title="Sport">Sport</a></li> - <li ><a href="http://www.smh.com.au/entertainment/" title="Entertainment">Entertainment</a></li> - <li ><a href="http://www.smh.com.au/lifestyle/" title="Life & Style">Life & Style</a></li> - <li ><a href="http://www.smh.com.au/travel/" title="Travel">Travel</a></li> - <li class="last"><a href="http://weather.smh.com.au/local.jsp" title="Weather">Weather</a></li> - </ul> - - <!-- Footer masthead links and copyright --> -<div class="fMastheadLinks cfix"> - <ul class="span-4 cfix"> - <li class="first"><h5>Sydney Morning Herald</h5></li> - <li><a href="http://www.smh.com.au/siteguide/" title="Sitemap">Sitemap</a></li> - <li><a href="http://www.smh.com.au/aboutsmh/index.html" title="About Us">About Us</a></li> - <li><a href="http://www.smh.com.au/contacts/" title="Contact Us">Contact Us</a></li> - <li><a href="http://www.fairfax.com.au/privacy" title="Privacy">Privacy</a></li> - <li><a href="http://www.fairfax.com.au/conditions" title="Conditions">Conditions</a></li> - <li><a href="http://www.fairfax.com.au/agreement" title="Member Agreement">Member Agreement</a></li> - <li><a href="http://www.adcentre.com.au/fairfax-digital-network.aspx" title="Advertise with Us">Advertise with Us</a></li> - </ul> - - <ul class="span-4 cfix"> - <li class="first"><h5>Products & Services</h5></li> - <li><a href="https://membercentre.fairfax.com.au/NewsletterSubscription.aspx" title="Newsletters">Newsletters</a></li> - <li><a href="http://www.smh.com.au/rssheadlines" title="RSS News Feeds">RSS News Feeds</a></li> - <li><a href="http://www.smh.com.au/mobile/" title="Mobile">Mobile</a></li> - <li><a href="http://www.smh.com.au/text/" title="Text">Text</a></li> - <li><a href="http://subscriptions.fairfax.com.au/smhlanding/" title="Subscribe">Subscribe</a></li> - </ul> - - <ul class="span-4 cfix"> - <li class="first"><h5>Classifieds</h5></li> - <li><a href="http://www.stayz.com.au/" title="Accommodation">Accommodation</a></li> - <li><a href="http://www.drive.com.au/" title="Cars">Cars</a></li> - <li><a href="http://www.rsvp.com.au/" title="Dating">Dating</a></li> - <li><a href="http://mycareer.com.au/" title="Jobs">Jobs</a></li> - <li><a href="http://www.domain.com.au/" title="Real Estate">Real Estate</a></li> - </ul> - - <ul class="span-4 cfix"> - <li class="first"><h5>Other Sites</h5></li> - <li><a href="http://www.theage.com.au/" title="The Age">The Age</a></li> - <li><a href="http://www.smh.com.au/" title="Sydney Morning Herald">Sydney Morning Herald</a></li> - <li><a href="http://www.watoday.com.au/" title="WA Today">WA Today</a></li> - <li><a href="http://www.brisbanetimes.com.au/" title="Brisbane Times">Brisbane Times</a></li> - <li><a href="http://www.businessday.com.au/" title="Business Day">Business Day</a></li> - <li><a href="http://www.moneymanager.com.au/" title="Money Manager">Money Manager</a></li> - <li><a href="http://www.essentialbaby.com.au/" title="Essential Baby">Essential Baby</a></li> - <li><a href="http://www.fairfax.com.au/map.ac" title="Fairfax Digital Network">Fairfax Digital Network</a></li> - </ul> - - <a class="footer-logo" href="http://www.fairfaxdigital.com.au/">Fairfax Digital</a> - <cite>Copyright &#169; - <script type="text/javascript">//<![CDATA[ - var today = new Date(); - document.write(today.getFullYear() + '.'); - //]]></script> - Fairfax Digital</cite> - -</div> -<!-- This comment fixes IE6's logo problem. Might be temporary until we've got content below this point --> - <!-- START Nielsen Online 2008 survey launch --> -<script type="text/javascript"> -var _rsCI="qt0303-fairfax"; var _rsND = "//secure-au.imrworldwide.com/"; -document.write('<scr' + 'ipt type="text/javascript" src="' + _rsND + 'cgi-bin/j?ci=' + _rsCI + '&se=1&te=0&rd=' + (new Date()).getTime() + '"><\/scr' + 'ipt>'); -</script> -<!-- END Nielsen Online --> -</div> -<!-- End fN-footerNetwork --> - <script type="text/javascript"> - function isInternalReferrer() { - var internalDomains = [ - "fairfaxdigital", - "smh", - "theage", - "watoday", - "brisbanetimes", - "smh", - "theage", - "brisbanetimes", - "watoday", - "businessday", - "tradingroom", - "moneymanager", - "investsmart", - "afr", - "afrboss", - "mysmallbusiness", - "newsbreak", - "rubgyheaven", - "realfooty", - "leaguehq", - "thevine", - "fairfax", - "sunherald", - "cracker", - "nationaltimes", - "essentialbaby", - "2ue", - "3aw", - "4bc", - "4bh", - "6pr", - "96fm", - "magic1278", - "mydj", - "rsvp", - "domain", - "homepriceguide", - "mycareer", - "thebigchair", - "drive", - "stayz", - "businessday" - ]; - - var referrer = document.referrer; - if (referrer == null || referrer == '') { - return true; - } - - var regex = new RegExp("//([a-z0-9_\\-\\.]+)[:0-9]*/"), - match = regex.exec(referrer, "gi"); - if (match && match.length > 1) { - for (var i in internalDomains) { - if (match[1].indexOf(internalDomains[i] + ".com.au") >= 0) { - return true; - } - } - } - - return false; - } - - - function getQueryVariable(variable) { - var query = window.location.search.substring(1); - var vars = query.split("&"); - for (var i=0;i<vars.length;i++) { - var pair = vars[i].split("="); - if (pair[0] == variable) { - return pair[1]; - } - } - return ''; - } - - function google_ad_request_done(google_ads) { - - function buildGoogleAd(googleAd) { - if (googleAd && googleAd.type == "text") { - var adContent = []; - adContent.push('<div style="margin-bottom:5px"><a href="'); - adContent.push(googleAd.url); - adContent.push('" style="text-decoration:none">'); - adContent.push('<h4 style="margin:0 5px;color:#039;font-size:13px;text-decoration:underline;font-weight:normal">'); - adContent.push(googleAd.line1); - adContent.push('</h4><p style="margin:0 5px;color:#000;font-size:12px">'); - adContent.push(googleAd.line2); - adContent.push(googleAd.line3); - adContent.push('</p><p style="margin:0 5px;color:#900">'); - adContent.push(googleAd.visible_url); - adContent.push('</p></a></div>'); - - return adContent.join(""); - } - } - - /* - * This function is required and is used to display - * the ads that are returned from the JavaScript - * request. You should modify the document.write - * commands so that the HTML they write out fits - * with your desired ad layout. - */ - var s = ''; - var i; - - /* - * Verify that there are actually ads to display. - */ - if (google_ads.length == 0) { - return; - } - - /* - * If an image or Flash ad is returned, display that ad. - * Otherwise, build a string containing all of the ads and - * then use a document.write() command to print that string. - */ - var googleAds = $("googleAds"), - moreGoogleAds = $("moreGoogleAds") - split = 1, - maxAdsInBlock = 3; - - if (googleAds) { - var allAds = []; - - window.addEvent("domready", function() { - // Populate the top adspot - var adsByGoogle = "<h4 style='margin: 5px; font-size: 12px;'>Ads by Google</h4>", - googleAdsContent = [], - moreGoogleAdsContent = [adsByGoogle], - adIndex = 0, - adsInMore = 0; - for (var i = 0, g = google_ads.length; i < g; i++) { - if (i < split) { - if (!isInternalReferrer()) { - if (i == 0) { - googleAdsContent.push(adsByGoogle); - } - googleAdsContent.push(buildGoogleAd(google_ads[adIndex])); - adIndex++; - } - } else if (adsInMore < maxAdsInBlock) { - moreGoogleAdsContent.push(buildGoogleAd(google_ads[adIndex])); - adsInMore++; - adIndex++; - } - } - var googleAdsContentString = googleAdsContent.join(""); - if (googleAdsContentString != "") { - googleAds.set('html', googleAdsContentString); - } else { - googleAds.destroy(); - } - moreGoogleAds.set('html', moreGoogleAdsContent.join("")); - }); - } - } - - // Sets the hints to be the keyword for google ads - var keyword = getQueryVariable('text'); - if (keyword != null && keyword != "") { - google_hints = keyword.replace("+", ","); - } - - google_ad_client = 'ca-fairfax-smh_js'; - google_ad_channel = 'Business'; - google_ad_output = 'js'; - google_max_num_ads = '4'; - google_ad_type = 'text'; - google_encoding = 'utf8'; - google_safe = 'high'; - google_ad_section = 'default'; -</script> - -<!-- - /* - * The JavaScript returned from the following page uses - * the parameter values assigned above to populate an array - * of ad objects. Once that array has been populated, - * the JavaScript will call the google_ad_request_done - * function to display the ads. - */ ---> -<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script> - <!--START Nielsen//NetRatings SiteCensus V5.2 --> -<!--COPYRIGHT 2006 Nielsen//NetRatings --> -<script type="text/javascript"> - var _rsCI="f2"; - var _rsCG="SMH-business-column-michaelpascoe-story-online"; - var _rsDN="//secure-au.imrworldwide.com/"; - var _rsCC=0; -</script> -<script type="text/javascript" src="http://secure-au.imrworldwide.com/v53.js"> -</script> -<noscript> - <img src="http://secure-au.imrworldwide.com/cgi-bin/m?ci=f2&amp;cg=SMH-business-column-michaelpascoe-story-online" alt="" /> -</noscript> -<!--END Nielsen//NetRatings SiteCensus V5.2 --> - -<!-- SiteCatalyst code version: H.20.2. -Copyright 1997-2009 Omniture, Inc. More info available at -http://www.omniture.com --> -<script language="JavaScript" type="text/javascript" src="http://resources.smh.com.au/smh/media-common-1.0/js/s_code.js"></script> -<script language="JavaScript" type="text/javascript"><!-- -/* You may give each page an identifying name, server, and channel on the next lines. */ -s.pageName="smh:article:business:column:michael pascoe:the board’s next fear the female quota" -s.server="" -s.channel="business" -s.pageType="" -s.prop3="smh:business:column:michael pascoe" -s.prop5="1017890" -s.prop13="michael pascoe" -s.prop2="online" -s.prop4="dcds:common templates" -s.prop1="business:the board’s next fear the female quota" -/* Conversion Variables */ -s.campaign="" -s.state="" -s.zip="" -s.events="event1" -s.products="" -s.purchaseID="" -s.hier1="business|column|michael pascoe|the board’s next fear: the female quota" -/************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/ -var s_code=s.t();if(s_code)document.write(s_code)//--></script> -<script language="JavaScript" type="text/javascript"><!-- -if(navigator.appVersion.indexOf('MSIE')>=0)document.write(unescape('%3C')+'\!-'+'-') -//--></script><noscript><img -src="http://f2nsmh.112.2O7.net/b/ss/f2nsmh/1/H.20.2--NS/0?[AQB]&cdp=3&[AQE]" -height="1" width="1" border="0" alt="" /></noscript><!--/DO NOT REMOVE/--> -<!-- End SiteCatalyst code version: H.20.2. --> - - <script type="text/javascript">if(window['FD']){function initPingServer(){var v = new Date().getTime();new Element("script",{src:"/action/pingServerAction?par=1017890&v=" + v}).inject($$("head")[0]);}FD.register("PingServer");}</script> - - <div id="adspot-1x1" class="ad"></div> - -<script type="text/javascript"> - if (!autoStartEnabled() && isInternalReferrer()) { - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-1x1", - iframeId: "adspot-1x1-iframe", - params: $merge($merge(FD.baseAd.params, { - aamsz : "1x1" - }),getAdParams("1x1")) - - }) - ); - } -); - } - -</script> - <div id="adspot-1x11" class="ad"></div> - -<script type="text/javascript"> - - delayedAds.push(function(){ - FD.addAd($merge(FD.baseAd, { - id: "adspot-1x11", - iframeId: "adspot-1x11-iframe", - params: $merge($merge(FD.baseAd.params, { - adtype: 'panorama', - aamsz : "1x11" - }),getAdParams("1x11")) - - }) - ); - } -); - -</script> -</div> - -</body> -</html> \ No newline at end of file diff --git a/src/test/resources/htmltests/smh-biz-article-1.html.gz b/src/test/resources/htmltests/smh-biz-article-1.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..dead7d1067e76fb94675a2ce3b3ff17e9605c16b GIT binary patch literal 20383 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 O{r>@T;@)A?fdK$G`^D`5 literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/xwiki-1324.html b/src/test/resources/htmltests/xwiki-1324.html deleted file mode 100644 index 2dd7c33dc5..0000000000 --- a/src/test/resources/htmltests/xwiki-1324.html +++ /dev/null @@ -1,1015 +0,0 @@ -<!DOCTYPE html> - <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" data-xwiki-reference="xwiki:XWiki.XWikiPreferences" data-xwiki-document="XWiki.XWikiPreferences" data-xwiki-wiki="xwiki" data-xwiki-space="XWiki" data-xwiki-page="XWikiPreferences" data-xwiki-isnew="false" data-xwiki-version="3.1" data-xwiki-rest-url="/xwiki/rest/wikis/xwiki/spaces/XWiki/pages/XWikiPreferences" data-xwiki-form-token="epgA3yUHjVWOMi0Zxgi4eQ" data-xwiki-user-reference="xwiki:XWiki.Admin" data-xwiki-locale=""> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>Global Administration - XWiki</title> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="shortcut icon" href="/xwiki/resources/icons/xwiki/favicon.ico?cache-version=1581617618000" /> - <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon16.png?cache-version=1581617618000" type="image/png" /> - <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon.svg?cache-version=1581617618000" type="image/svg+xml" /> - <link rel="apple-touch-icon" href="/xwiki/resources/icons/xwiki/favicon144.png?cache-version=1581617618000" /> - <link rel="canonical" href="/xwiki/bin/view/XWiki/XWikiPreferences" /> - <meta name="revisit-after" content="7 days" /> -<meta name="description" content="Global Administration" /> -<meta name="keywords" content="wiki" /> -<meta name="rating" content="General" /> -<meta name="author" content="superadmin" /> -<link rel="alternate" type="application/rss+xml" title="Wiki Feed RSS" href="/xwiki/bin/view/Main/WebRss?xpage=rdf" /> -<link rel="alternate" type="application/rss+xml" title="Blog RSS Feed" href="/xwiki/bin/view/Blog/GlobalBlogRss?xpage=plain" /> - <link href="/xwiki/webjars/wiki%3Axwiki/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.min.css" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/xwiki-platform-tree-webjar/12.1-SNAPSHOT/tree.min.css?evaluate=true" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/selectize.js/0.12.5/css/selectize.bootstrap3.css" type='text/css' rel='stylesheet'/> - <link href="/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/css/drawer.min.css" rel="stylesheet" type="text/css" /> - - - - - -<link href=" /xwiki/bin/skin/skins/flamingo/style.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg - " rel="stylesheet" type="text/css" media="all" /> -<link href=" /xwiki/bin/skin/skins/flamingo/print.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg - " rel="stylesheet" type="text/css" media="print" /> - <!--[if IE]> - <link href=" /xwiki/bin/skin/skins/flamingo/ie-all.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg - " rel="stylesheet" type="text/css" /> -<![endif]--> - - <link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/css/xwiki-min.css?cache-version=1581618408000&colorTheme=FlamingoThemes.Iceberg&amp;language=en'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/widgets/validation/livevalidation.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/suggest/xwiki.selectize.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/js/xwiki/table/livetable.css?cache-version=1581618404000'/> - <link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Style?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Tour/HomepageTour/WebHome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Settings?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminSheet?language=en&amp;docVersion=1.1&amp;colorTheme=FlamingoThemes.Iceberg" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Panels/Applications?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Help/SupportPanel/WebHome?language=en&amp;docVersion=1.1" /> - - <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/requirejs/2.3.6/require.min.js?r=1" - data-wysiwyg="true"></script> - - <script type='text/javascript' src='/xwiki/resources/js/prototype/prototype.js?cache-version=1581618402000'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/js/scriptaculous/effects.js?cache-version=1581618404000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/xwiki-min.js?cache-version=1581618408000&defer=false&amp;language=en'></script> -<script type='text/javascript' src='/xwiki/bin/skin/skins/flamingo/flamingo.min.js?cache-version=1581618398000&language=en' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.js?cache-version=1581617618000&h=1255116547' defer='defer'></script> -<script type='text/javascript' src='/xwiki/resources/uicomponents/lock/lock.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/resources/uicomponents/widgets/validation/livevalidation_prototype.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/async/async.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/hierarchy/hierarchy.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/widgets/tree.min.js?cache-version=1581618398000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/suggest/suggestUsersAndGroups.js?cache-version=1581618406000&language=en' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/table/livetable.js?cache-version=1581618404000' defer='defer'></script> - - <script type='text/javascript' src='/xwiki/bin/jsx/TourCode/TourJS?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Settings?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Script?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/QuickSearchUIX?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminSheet?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/Panels/Applications?language=en&amp;docVersion=1.1' defer='defer'></script> - - -<script type="text/javascript" src="/xwiki/resources/js/xwiki/compatibility.js?cache-version=1581618404000" defer="defer"></script> -<script type="text/javascript" src="/xwiki/resources/js/xwiki/markerScript.js?cache-version=1581618404000" defer="defer"></script> -<!--[if lt IE 9]> - <script src="/xwiki/webjars/wiki%3Axwiki/html5shiv/3.7.2/html5shiv.min.js?r=1"></script> - <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/respond/1.4.2/dest/respond.min.js?r=1" defer="defer"></script> -<![endif]--> -<script type="text/javascript" data-wysiwyg="true"> -// <![CDATA[ -require.config({ - paths: { - 'jquery': '/xwiki/webjars/wiki%3Axwiki/jquery/2.2.4/jquery.min.js?r=1', - 'bootstrap': '/xwiki/webjars/wiki%3Axwiki/bootstrap/3.4.1/js/bootstrap.min.js?r=1', - 'xwiki-meta': '/xwiki/resources/js/xwiki/meta.js?cache-version=1581618404000', - 'xwiki-events-bridge': "/xwiki/resources/js/xwiki/eventsBridge.js?cache-version=1581618404000", - 'iscroll': '/xwiki/webjars/wiki%3Axwiki/iscroll/5.1.3/build/iscroll-lite.js?r=1', - 'drawer': '/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/js/jquery.drawer.min.js?r=1', - 'deferred': "/xwiki/resources/uicomponents/require/deferred.js?cache-version=1581618406000" - }, - shim: { - 'bootstrap' : ['jquery'], - 'drawer': ['jquery', 'iscroll'] - }, - map: { - '*': { - 'jquery': 'jQueryNoConflict' - }, - 'jQueryNoConflict': { - 'jquery': 'jquery' - }, - } -}); -define('jQueryNoConflict', ['jquery'], function ($) { - return $.noConflict(); -}); -if (window.Prototype && Prototype.BrowserFeatures.ElementExtensions) { - require(['jquery', 'bootstrap'], function ($) { - // Fix incompatibilities between BootStrap and Prototype - var disablePrototypeJS = function (method, pluginsToDisable) { - var handler = function (event) { - event.target[method] = undefined; - setTimeout(function () { - delete event.target[method]; - }, 0); - }; - pluginsToDisable.each(function (plugin) { - $(window).on(method + '.bs.' + plugin, handler); - }); - }, - pluginsToDisable = ['collapse', 'dropdown', 'modal', 'tooltip', 'tab', 'popover']; - disablePrototypeJS('show', pluginsToDisable); - disablePrototypeJS('hide', pluginsToDisable); - }); -} -require(['jquery', 'drawer'], function($) { - $(document).ready(function() { - $('.drawer-main').closest('body').drawer(); - }); -}); -window.XWiki = window.XWiki || {}; -XWiki.webapppath = "xwiki/"; -XWiki.servletpath = "bin/"; -XWiki.contextPath = "/xwiki"; -XWiki.mainWiki = "xwiki"; -// Deprecated: replaced by meta data in the HTML element -XWiki.currentWiki = "xwiki"; -XWiki.currentSpace = "XWiki"; -XWiki.currentPage = "XWikiPreferences"; -XWiki.editor = "globaladmin"; -XWiki.viewer = ""; -XWiki.contextaction = "admin"; -XWiki.skin = 'XWiki.DefaultSkin'; -XWiki.docisnew = false; -XWiki.docsyntax = "xwiki/2.0"; -XWiki.docvariant = ""; -XWiki.blacklistedSpaces = [ ]; -XWiki.hasEdit = true; -XWiki.hasProgramming = true; -XWiki.hasBackupPackImportRights = true; -XWiki.hasRenderer = true; -window.docviewurl = "/xwiki/bin/view/XWiki/XWikiPreferences"; -window.docediturl = "/xwiki/bin/edit/XWiki/XWikiPreferences"; -window.docsaveurl = "/xwiki/bin/save/XWiki/XWikiPreferences"; -window.docgeturl = "/xwiki/bin/get/XWiki/XWikiPreferences"; -// ]]> -</script> - - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> - <body id="body" class="skin-flamingo wiki-xwiki space-XWiki viewbody content panel-left-width-Medium panel-right-width-Medium drawer drawer-right drawer-close"> -<div id="xwikimaincontainer"> -<div id="xwikimaincontainerinner"> - - <div id="menuview"> - - - - - - - - - - - - - - - - - - - - - <nav class="navbar navbar-default actionmenu"> - <div class="container-fluid"> - <div class="navbar-header"> - <div id="companylogo"> - <a href="/xwiki/bin/view/Main/" title="Home" rel="home" class="navbar-brand"> - <img src="/xwiki/bin/download/FlamingoThemes/Iceberg/logo.svg?rev=1.1" alt="Wiki Logo"/> - </a> -</div> - - </div> - <div id="xwikimainmenu"> - - <ul class="nav navbar-nav navbar-left"> - <li class="divider" role="separator"></li> - </ul> - - <ul class="nav navbar-nav navbar-right"> - <li> - <a class="icon-navbar drawer-toggle" id="tmDrawerActivator" title="Drawer"><span class="sr-only">Toggle navigation</span><span class="fa fa-bars"></span></a> - </li> - <li class="navbar-avatar"> -<a href="/xwiki/bin/view/XWiki/Admin" class="icon-navbar"> -<span class="sr-only">User Profile</span> - <img class='avatar avatar_50' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/></a> -</li> - -<li class="dropdown" id="tmNotifications"> -<a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" -title="Notifications"> -<span class="sr-only"> -Toggle navigation -</span> -<span class="fa fa-bell"></span> -</a> -<ul class="dropdown-menu"> - <li> -<div class="notifications-header"> -<div class="clearfix"> -<div class="col-xs-4"> -<p><strong>Notifications</strong></p> -</div> -<div class="col-xs-8 text-right"> -<p> -<span class="notifications-header-link"> -<a href="/xwiki/bin/get/XWiki/Notifications/Code/NotificationRSSService?outputSyntax=plain" -class="notifications-header-link notifications-rss-link" rel="nofollow external"> -<span class="fa fa-rss"></span>&nbsp;RSS Feed -</a> -</span> -<span class="notifications-header-link"> -<a href="/xwiki/bin/view/XWiki/Admin?category=notifications" class="notifications-settings" rel="nofollow"> -<span class="fa fa-cog"></span>&nbsp;Settings -</a> -</span> -</p> -</div> -</div> -<div class="notifications-toggles clearfix" data-enabled="false"> -<pre> -<label class="hidden" for="notificationPageOnly">Toggle Page notifications only</label><input type="checkbox" id="notificationPageOnly" name="notificationPageOnly" checked="checked"/><label class="hidden" for="notificationPageAndChildren">Toggle Page and children notifications</label><input type="checkbox" id="notificationPageAndChildren" name="notificationPageAndChildren" checked="checked"/><label class="hidden" for="notificationWiki">Toggle Wiki notifications</label><input type="checkbox" id="notificationWiki" name="notificationWiki" checked="checked"/></pre> -</div> -<div class="notifications-header-uix col-xs-12"> -</div> -</div> -<div class="notifications-area loading clearfix"> -</div> -</li> - -</ul> -</li> - <li> -<form class="navbar-form globalsearch globalsearch-close form-inline" id="globalsearch" action="/xwiki/bin/view/Main/Search"> -<label class="hidden" for="headerglobalsearchinput">Search</label> -<input type="text" name="text" placeholder="search..." id="headerglobalsearchinput" /> -<button type="submit" class="btn" title="Search"><span class="fa fa-search"></span></button> -</form> -</li> - </ul> - - </div> </div> </nav> - - - - - - - -<div class="drawer-main drawer-default" id="tmDrawer"> - <nav class="drawer-nav"> - - <div class="drawer-brand clearfix"> - <a href="/xwiki/bin/view/XWiki/Admin"> - <img class='avatar avatar_120' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/> </a> - <div class="brand-links"> - <a href="/xwiki/bin/view/XWiki/Admin" class="brand-user" id="tmUser">Administrator</a> - <a href="/xwiki/bin/logout/XWiki/XWikiLogout?xredirect=%2Fxwiki%2Fbin%2Fadmin%2FXWiki%2FXWikiPreferences%3Feditor%3Dglobaladmin%26section%3DGroups" id="tmLogout" rel="nofollow"><span class="fa fa-sign-out"></span> Log-out</a> - </div> - </div> - - <ul class="drawer-menu"> - <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Home</li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/admin/XWiki/XWikiPreferences" id="tmAdminWiki" rel="nofollow"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-wrench"></span> - </div> - <div class="drawer-menu-item-text">Administer Wiki</div> - </a> - </li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/Main/AllDocs" id="tmWikiDocumentIndex"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-book"></span> - </div> - <div class="drawer-menu-item-text">Page Index</div> - </a> - </li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/Main/UserDirectory" id="tmMainUserIndex"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-user"></span> - </div> - <div class="drawer-menu-item-text">User Index</div> - </a> - </li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/Applications/" id="tmMainApplicationIndex"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-th"></span> - </div> - <div class="drawer-menu-item-text">Application Index</div> - </a> - </li> - <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Global</li> - - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/WikiManager/"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-list-alt"></span> - </div> - <div class="drawer-menu-item-text">Wiki Index</div> - </a> - </li> - - </ul> - </nav> -</div> - - - - - - </div> - <div id="headerglobal"> - <div id="globallinks"> - </div> <div class="clearfloats"></div> - - - </div> - - -<div class="content" id="contentcontainer"> -<div id="contentcontainerinner"> -<div class="leftsidecolumns"> - <div id="contentcolumn"> - <div class="main layoutsubsection"> -<div id="mainContentArea"> - - - - - - - - - - - - - - - - - - - - - - - - - <ol class="breadcrumb breadcrumb-expandable" data-entities="{xwiki:XWiki.XWikiPreferences=XWiki.XWikiPreferences}" data-entity="XWiki.XWikiPreferences" data-id="hierarchy" data-limit="5" data-treenavigation="true" id="hierarchy"><li class="wiki dropdown"><a href="/xwiki/bin/view/Main/"><span class="fa fa-home"></span></a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10"></div> -</div></li><li class="space dropdown"><a href="/xwiki/bin/view/XWiki/">XWiki</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.WebHome" data-responsive="true" data-root="{&quot;type&quot;:&quot;wiki&quot;,&quot;id&quot;:&quot;xwiki&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=wiki%3Axwiki"></div> -</div></li><li class="document active dropdown"><a href="/xwiki/bin/view/XWiki/XWikiPreferences">Global Administration</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{&quot;type&quot;:&quot;document&quot;,&quot;id&quot;:&quot;xwiki:XWiki.WebHome&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=document%3Axwiki%3AXWiki.WebHome"></div> -</div> </li></ol> - <div id="document-title"><h1 id="HGlobalAdministration:Groups" class="wikigeneratedid wikigeneratedheader"><span>Global Administration: Groups</span></h1><div class="noitems"><p>Manage user groups: add or remove groups, or change group members.</p></div><hr/></div><div id="administration-menu" class="panel-group admin-menu" role="tablist" aria-multiselectable="true"> -<div class="panel xform"> -<label for="adminsearchmenu" class="hidden">Search</label> -<input type="text" class="form-control panel-group-filter" autocomplete="off" id="adminsearchmenu" -placeholder="Search for..." /> -</div> - <div class="panel panel-default"> -<a class="panel-heading" role="tab" id="panel-heading-usersgroups" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=0" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-usersgroups" aria-expanded="true" aria-controls="panel-body-usersgroups" -title="Manage users, groups, and their access rights."><span class="fa fa-group"></span>Users &#38; Rights</a> -<div class="panel-collapse collapse in" role="tabpanel" id="panel-body-usersgroups" -aria-labelledby="panel-heading-usersgroups"> -<div class="list-group"> -<a class="list-group-item" data-id="Users" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Users" title="Manage users of this wiki: add, remove, modify their profile information." ->Users</a> -<a class="list-group-item active" data-id="Groups" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Groups" title="Manage user groups: add or remove groups, or change group members." ->Groups</a> -<a class="list-group-item" data-id="Rights" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Rights" title="Manage groups and users rights: control who can view, edit and delete pages." ->Rights</a> -<a class="list-group-item" data-id="UserProfile" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=UserProfile" title="Manage what information is displayed on the user profile of each user." ->User Profile</a> -<a class="list-group-item" data-id="userdirectory" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=userdirectory" title="Customize the user directory live table." ->User Directory</a> -<a class="list-group-item" data-id="Registration" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Registration" title="Manage user registration settings." ->Registration</a> -<a class="list-group-item" data-id="Invitation" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Invitation" title="Configure the Invitation Application" ->Invitation</a> -<a class="list-group-item" data-id="Authentication" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Authentication" title="" ->Authentication</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-extensionmanager" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=1" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-extensionmanager" aria-expanded="false" aria-controls="panel-body-extensionmanager" -title="Search, add, upgrade and remove extensions."><span class="fa fa-puzzle-piece"></span>Extensions</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-extensionmanager" -aria-labelledby="panel-heading-extensionmanager"> -<div class="list-group"> -<a class="list-group-item" data-id="XWiki.Extensions" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.Extensions" title="Search for new extensions to add to the wiki." ->Extensions</a> -<a class="list-group-item" data-id="XWiki.ExtensionHistory" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionHistory" title="See the history of the installed extensions." ->History</a> -<a class="list-group-item" data-id="XWiki.ExtensionUpdater" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionUpdater" title="Check if there are any updates available for the installed extensions." ->Updater</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-lf" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=2" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-lf" aria-expanded="false" aria-controls="panel-body-lf" -title="Change the aspect and layout of the wiki."><span class="fa fa-columns"></span>Look &#38; Feel</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-lf" -aria-labelledby="panel-heading-lf"> -<div class="list-group"> -<a class="list-group-item" data-id="Themes" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Themes" title="Customize the color and icon themes, skin and logo." ->Themes</a> -<a class="list-group-item" data-id="menu.name" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=menu.name" title="" ->Menus</a> -<a class="list-group-item" data-id="Panels.PanelWizard" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Panels.PanelWizard" title="Add and remove panels, change the page layout." ->Panels</a> -<a class="list-group-item" data-id="panels.applications" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.applications" title="Manage what applications are displayed in the Applications Panel." ->Applications Panel</a> -<a class="list-group-item" data-id="panels.navigation" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.navigation" title="Manage what pages are displayed in the Navigation Panel." ->Navigation Panel</a> -<a class="list-group-item" data-id="Presentation" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Presentation" title="Choose the page tabs that are visible and configure the page header and footer." ->Presentation</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-content" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=3" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-content" aria-expanded="false" aria-controls="panel-body-content" -title="Manipulate the content of the wiki."><span class="fa fa-file-text-o"></span>Content</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-content" -aria-labelledby="panel-heading-content"> -<div class="list-group"> -<a class="list-group-item" data-id="Templates" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Templates" title="Settings for the creation of page templates." ->Page Templates</a> -<a class="list-group-item" data-id="Localization" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Localization" title="Language-related settings." ->Localization</a> -<a class="list-group-item" data-id="Import" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Import" title="Import pages or applications into the wiki." ->Import</a> -<a class="list-group-item" data-id="Export" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Export" title="Export wiki pages into a XAR." ->Export</a> -<a class="list-group-item" data-id="Annotations" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Annotations" title="Configure the page annotations" ->Annotations</a> -<a class="list-group-item" data-id="XWiki.OfficeImporterAdmin" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.OfficeImporterAdmin" title="Configure the Office Server." ->Office Server</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-edit" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=4" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-edit" aria-expanded="false" aria-controls="panel-body-edit" -title="Configure the edit mode, the WYSIWYG editor and the available syntaxes."><span class="fa fa-pencil"></span>Editing</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-edit" -aria-labelledby="panel-heading-edit"> -<div class="list-group"> -<a class="list-group-item" data-id="Editing" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Editing" title="Choose the default edit mode and configure its title and versioning parameters." ->Edit Mode</a> -<a class="list-group-item" data-id="WYSIWYG" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=WYSIWYG" title="Choose the default WYSIWYG editor and configure it." ->WYSIWYG Editor</a> -<a class="list-group-item" data-id="Syntaxes" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Syntaxes" title="Choose the default page syntax and configure the available markup syntaxes." ->Syntaxes</a> -<a class="list-group-item" data-id="Name Strategies" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Name%20Strategies" title="" ->Name Strategies</a> -<a class="list-group-item" data-id="SyntaxHighlighting" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=SyntaxHighlighting" title="" ->Syntax Highlighting</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-email" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=5" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-email" aria-expanded="false" aria-controls="panel-body-email" -title="Configure mail sending parameters and view mail statuses."><span class="fa fa-envelope-o"></span>Mail</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-email" -aria-labelledby="panel-heading-email"> -<div class="list-group"> -<a class="list-group-item" data-id="emailSend" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailSend" title="Configure mail sending parameters." ->Mail Sending</a> -<a class="list-group-item" data-id="emailStatus" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailStatus" title="View the statuses of sent mails and resend mails that resulted in failure." ->Mail Sending Status</a> -<a class="list-group-item" data-id="emailGeneral" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailGeneral" title="Configure advanced mail parameters, like mail address obfuscation." ->Advanced</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-search" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=6" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-search" aria-expanded="false" aria-controls="panel-body-search" -title="Choose the default search engine or configure the search index."><span class="fa fa-search"></span>Search</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-search" -aria-labelledby="panel-heading-search"> -<div class="list-group"> -<a class="list-group-item" data-id="Search" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Search" title="Choose the default search engine or configure the search index." ->Search</a> -<a class="list-group-item" data-id="searchSuggest" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=searchSuggest" title="Configure the search suggest options." ->Search Suggest</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-wikis" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=7" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-wikis" aria-expanded="false" aria-controls="panel-body-wikis" -title="Wikis management."><span class="fa fa-globe"></span>Wikis</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-wikis" -aria-labelledby="panel-heading-wikis"> -<div class="list-group"> -<a class="list-group-item" data-id="wikis.descriptor" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.descriptor" title="Configure the wiki descriptor" ->Descriptor</a> -<a class="list-group-item" data-id="wikis.templates" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.templates" title="Manage the wiki templates" ->Wiki Templates</a> -<a class="list-group-item" data-id="wikis.rights" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.rights" title="" ->Creation Right</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-other" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=8" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-other" aria-expanded="false" aria-controls="panel-body-other" -title="Various configurations for extensions."><span class="fa fa-wrench"></span>Other</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-other" -aria-labelledby="panel-heading-other"> -<div class="list-group"> -<a class="list-group-item" data-id="Notifications" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Notifications" title="" ->Notifications</a> -<a class="list-group-item" data-id="Logging" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Logging" title="Review and modify the log level associated to a registered logger." ->Logging</a> -<a class="list-group-item" data-id="captcha" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=captcha" title="" ->CAPTCHA</a> -<a class="list-group-item" data-id="analytics" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=analytics" title="Configure the Google Analytics™ account." ->Google Analytics™</a> -<a class="list-group-item" data-id="MessageStream" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=MessageStream" title="Enable or disable the message stream in the wiki." ->Message Stream</a> -</div> -</div> -</div> -<div class="panel panel-default noitems hidden"> -<div class="panel-heading collapsed"> -No results. -</div> -</div> -</div><div id="admin-page-content"> - <div class="medium-avatars"> - <div class="xwiki-livetable-container"> - <table id="groupstable" class="xwiki-livetable"> - <tr> - <td class="xwiki-livetable-pagination"> - <span id="groupstable-limits" class="xwiki-livetable-limits"></span> - <span id="groupstable-pagesize" class="xwiki-livetable-pagesize"> - <span>per page of</span> - <span class="xwiki-livetable-pagesize-content" ></span> - </span> - <span id="groupstable-ajax-loader" class="xwiki-livetable-loader hidden"> - <img src="/xwiki/resources/icons/xwiki/ajax-loader-large.gif?cache-version=1581617618000" alt="Loading..." title="" /> - </span> - <span class="controlPagination"> - <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> - <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> - </span> - <span class="pagination"> - <span class="xwiki-livetable-pagination-text">Page</span> - <span class="xwiki-livetable-pagination-content" ></span> - </span> - </td> - </tr> - <tr> - <td class="xwiki-livetable-display-container"> - <table class="xwiki-livetable-display"> - <thead class="xwiki-livetable-display-header"> - <tr> - <th scope="col" class="xwiki-livetable-display-header-text - "> - <label for="xwiki-livetable-groupstable-filter-1"> Group Name - </label> </th> - <th scope="col" class="xwiki-livetable-display-header-text - "> - Members - </th> - <th scope="col" class="xwiki-livetable-display-header-text actions - "> - Actions - </th> - </tr> - <tr class="xwiki-livetable-display-filters"> - <td class="xwiki-livetable-display-header-filter"> - <input id="xwiki-livetable-groupstable-filter-1" name="name" type="text" - title="Filter for the Group Name column" /> - </td> - <td class="xwiki-livetable-display-header-filter"> - </td> - <td class="xwiki-livetable-display-header-filter"> - </td> - </tr> - <tr class="xwiki-livetable-initial-message"> - <td colspan="3"> - <div class="warningmessage">The environment prevents the table from loading data.</div> - </td> - </tr> - </thead> - <tbody id="groupstable-display" class="xwiki-livetable-display-body"><tr><td>&nbsp;</td></tr></tbody> - </table> - </td> - </tr> - <tr> - <td class="xwiki-livetable-pagination"> - <span class="xwiki-livetable-limits"></span> - <span class="controlPagination"> - <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> - <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> - </span> - <span class="pagination"> - <span class="xwiki-livetable-pagination-text">Page</span> - <span class="xwiki-livetable-pagination-content" ></span> - </span> - </td> - </tr> - </table> - <div id="groupstable-inaccessible-docs" class="hidden"> - <div class="infomessage">(*) Some pages require special rights to be viewed.</div> - </div> - <div id="groupstable-computed-title-docs" class="hidden"> - <div class="infomessage">(<span class='docTitleComputed'></span>)&nbsp;Some pages have a computed title. Filtering and sorting by title will not work as expected for these pages.</div> - </div> - <script type="text/javascript"> - //<![CDATA[ -(function() { - var startup = function(container) { - // Make sure the LiveTable code is loaded (the WYSIWYG editor doesn't load the JavaScript code for instance). - var liveTableCodeLoaded = XWiki && XWiki.widgets && XWiki.widgets.LiveTable; - // Also make sure the live table is not already initialized. - var liveTableElement = $('groupstable'); - if (liveTableCodeLoaded && liveTableElement && !liveTableElement.__liveTable && - (liveTableElement == container || liveTableElement.descendantOf(container))) { - window["livetable_groupstable"] = liveTableElement.__liveTable = new XWiki.widgets.LiveTable("/xwiki/bin/view/XWiki/XWikiPreferences?xpage=getgroups", - "groupstable", function (row, i, table) { - // This callback method has been generated from Velocity. - var columns = ["name","members","_actions"]; - var columnDescriptors = {"name":{"type":"text","html":true,"sortable":false,"headerClass":null,"displayName":"Group Name"},"members":{"filterable":false,"sortable":false,"headerClass":null,"displayName":"Members"},"scope":{"type":"list","sortable":false,"displayName":"Scope"},"_actions":{"actions":[{"id":"edit","label":"edit","async":null,"callback":null,"icon":"<span class=\"fa fa-pencil\"></span>"},{"id":"delete","label":"delete","async":null,"callback":null,"icon":"<span class=\"fa fa-times\"></span>"}],"labels":{"delete":"delete"},"filterable":false,"headerClass":"actions","displayName":"Actions"}}; - var className = ""; - var showFilterNote = false; - if (!row['doc_viewable']) { - $("groupstable-inaccessible-docs").removeClassName('hidden'); - } - var tr = new Element('tr'); - columns.forEach(function(column) { - var descriptor = columnDescriptors[column] || {}; - if (descriptor.type === 'hidden') { - return; - } - // The column's display name to be used when displaying the reponsive version. - var displayName = descriptor.displayName || column; - var fieldName = column.replace(/^doc\./, 'doc_'); - if (column === '_actions') { - var adminActions = ['admin', 'rename', 'rights']; - var td = new Element('td', { - 'class': 'actions', - 'data-title': displayName - }); - td.toggleClassName('hide-labels', descriptor.labels === false); - (descriptor.actions || []).forEach(function(action, index) { - if (row['doc_has' + action.id] || action.id === 'view' || (row['doc_has' + action.id] === undefined && - (row['doc_hasadmin'] || adminActions.indexOf(action.id) < 0))) { - var link = new Element('a', { - 'href': row['doc_' + action.id + '_url'] || row['doc_url'], - 'class': 'action action' + action.id - }).update('<span class="action-icon"></span><span class="action-label"></span>'); - link.down('.action-icon').update(action.icon).writeAttribute('title', action.label); - link.down('.action-label').update(action.label.escapeHTML()); - if (action.async) { - link.observe('click', function(event) { - event.stop(); - new Ajax.Request(this.href, { - onSuccess: function() { - eval(action.callback); - } - }); - }.bindAsEventListener(link)); - } - td.insert(link); - } - }); - tr.appendChild(td); - } else { - var td = new Element('td', { - 'class': [ - fieldName, - 'link' + (descriptor.link || ''), - 'type' + (descriptor.type || '') - ].join(' '), - 'data-title': displayName - }); - var container = td; - if (descriptor.link && row['doc_viewable']) { - var link = new Element(descriptor.link === 'editor' ? 'span' : 'a'); - // Automatic: the link URL is in JSON results, with the '_url' sufix. - if (descriptor.link === 'auto') { - link.href = row[fieldName + '_url'] || row['doc_url']; - } else if (descriptor.link === 'field') { - if (row[fieldName + '_url']) { - link.href = row[fieldName + '_url']; - } - // Property editor - } else if (descriptor.link === 'editor') { - var propertyClassName = descriptor['class'] || className; - td.observe('click', function(event) { - var tag = event.element().down('span') || event.element(); - editProperty(row['doc_fullName'], propertyClassName, column, function(value) { - tag.innerHTML = value; - }); - }); - // Author, space or wiki link. - } else if (row['doc_' + descriptor.link + '_url']) { - link.href = row['doc_' + descriptor.link + '_url']; - } else { - link.href = row['doc_url']; - } - td.appendChild(link); - container = link; - } - // The value can be passed as a string.. - if (descriptor.html + '' === 'true') { - container.innerHTML = row[fieldName] || ''; - } else if (row[fieldName] !== undefined && row[fieldName] !== null) { - var text = row[fieldName] + ''; - if (fieldName === 'doc_name' && !row['doc_viewable']) { - text += '*'; - } - if (showFilterNote && fieldName === 'doc_title' && row['doc_title_raw'] !== undefined) { - container.addClassName('docTitleComputed'); - } - container.update(text.escapeHTML()); - } - tr.appendChild(td); - } - }); - return tr; -} -, {"maxPages":10,"limit":15,"selectedTags":[]}); - document.observe("xwiki:livetable:groupstable:loadingEntries", function() { - $('groupstable-pagesize').addClassName("hidden"); - }); - document.observe("xwiki:livetable:groupstable:loadingComplete", function() { - $('groupstable-pagesize').removeClassName("hidden"); - }); - return true; - } - return false; - }; - var init = function(event) { - var elements = (event && event.memo.elements) || [$('body')]; - return elements.length > 0 && elements.some(startup); - }; - // Initialize the live table on page load or after the live table code has been loaded. - (XWiki && XWiki.isInitialized && init()) || document.observe('xwiki:livetable:loading', init); - // Initialize the live table when it is added after the page is loaded. - document.observe('xwiki:dom:updated', init); -})(); - //]]> - </script> -</div></div> -<p> -<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#createGroupModal"> -Create group -</button> -</p> -<div class="modal" id="createGroupModal" tabindex="-1" role="dialog" -aria-labelledby="createGroupModal-label" data-backdrop="static" data-keyboard="false"> -<div class="modal-dialog" role="document"> -<form class="modal-content xform"> -<div class="modal-header"> -<button type="button" class="close" data-dismiss="modal" aria-label="Close"> -<span aria-hidden="true">&times;</span> -</button> -<div class="modal-title" id="createGroupModal-label"> -Create group -</div> -</div> -<div class="modal-body"> -<div class="hidden"> -<input type="hidden" name="form_token" value="epgA3yUHjVWOMi0Zxgi4eQ" /> -<input type="hidden" name="template" value="XWiki.XWikiGroupTemplate" /> -</div> -<dl> -<dt> -<label for="createGroupModal-groupName" class="sr-only"> -Group Name -</label> -</dt> -<dd class="form-group has-feedback"> -<input type="text" class="form-control" id="createGroupModal-groupName" name="name" autocomplete="off" -placeholder="Group Name" /> -<span class="form-control-feedback loading hidden" aria-hidden="true"></span> -<span class="form-control-feedback success hidden" aria-hidden="true"><span class="fa fa-check"></span></span> -<span class="form-control-feedback error hidden" aria-hidden="true"><span class="fa fa-times"></span></span> -<span class="help-block hidden"></span> -</dd> -</dl> -</div> -<div class="modal-footer"> -<button type="button" class="btn btn-default" data-dismiss="modal"> -Cancel -</button> -<button type="submit" class="btn btn-primary"> -Create -</button> -</div> -</form> -</div> -</div> - -<div class="modal" id="editGroupModal" tabindex="-1" role="dialog" aria-labelledby="editGroupModal-label" -data-backdrop="static" data-keyboard="false" data-liveTable="#groupstable" data-liveTableAction="edit"> -<div class="modal-dialog" role="document"> -<div class="modal-content"> -<div class="modal-header"> -<button type="button" class="close" data-dismiss="modal" aria-label="Close"> -<span aria-hidden="true">&times;</span> -</button> -<div class="modal-title" id="editGroupModal-label"> -Edit group -</div> -</div> -<div class="modal-body"></div> -</div> -</div> -</div> - -<div class="modal" id="deleteGroupModal" tabindex="-1" role="dialog" aria-labelledby="deleteGroupModal-label" -data-liveTable="#groupstable" data-liveTableAction="delete"> -<div class="modal-dialog" role="document"> -<div class="modal-content"> -<div class="modal-header"> -<button type="button" class="close" data-dismiss="modal" aria-label="Close"> -<span aria-hidden="true">&times;</span> -</button> -<div class="modal-title" id="deleteGroupModal-label"> -Delete group -</div> -</div> -<div class="modal-body"> -<p>The group <span class="groupName"></span> will be deleted. Are you sure you want to proceed?</p> -</div> -<div class="modal-footer"> -<button type="button" class="btn btn-default" data-dismiss="modal"> -Cancel -</button> -<button type="submit" class="btn btn-danger" data-dismiss="modal"> -Delete -</button> -</div> -</div> -</div> -</div> -</div> - <div class="clearfloats"></div> -</div></div></div><div id="leftPanels" class="panels left panel-width-Medium"> - <div class="panel expanded PanelsApplications Applications"><h1 class="xwikipaneltitle">Applications</h1><div class="xwikipanelcontents"><ul class="applicationsPanel nav nav-pills nav-stacked"> -<li> -<a href="/xwiki/bin/view/Dashboard/" title="Dashboard"> -<span class="application-img"><span class="fa fa-th-large"></span></span> -<span class="application-label">Dashboard</span> -</a> -</li> -<li> -<a href="/xwiki/bin/view/Help/" title="Help"> -<span class="application-img"><span class="fa fa-question-circle"></span></span> -<span class="application-label">Help</span> -</a> -</li> -<li> -<a href="/xwiki/bin/view/Sandbox/" title="Sandbox"> -<span class="application-img"><span class="fa fa-coffee"></span></span> -<span class="application-label">Sandbox</span> -</a> -</li> -</ul> -<ul class="applicationsPanel applicationsPanelMoreList nav nav-pills nav-stacked"> -<li> -<a class="applicationPanelMoreButton" href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.AddExtensions" title="More applications"> -<span class="application-img"><span class="fa fa-plus"></span></span> -<span class="application-label">More applications</span> -</a> -<div class="applicationPanelMoreContainer hidden"> -<ul class="nav nav-pills nav-stacked"> -<li> -<a href="/xwiki/bin/view/AppWithinMinutes/" title="Create your own!"> -<span class="application-img"><span class="fa fa-caret-right"></span></span> -<span class="application-label">Create your own!</span> -</a> -</li> -<li> -<a href="/xwiki/bin/view/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.Extensions" title="Install new applications"> -<span class="application-img"><span class="fa fa-caret-right"></span></span> -<span class="application-label">Install new applications</span> -</a> -</li> -</ul> -</div> -</li> -</ul></div></div> - <div class="panel expanded PanelsNavigation Navigation"><h1 class="xwikipaneltitle">Navigation</h1><div class="xwikipanelcontents"> - - - - - - - - <div class = "xtree" data-responsive = "true" data-url = "/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&#38;sheet=XWiki.DocumentTree&#38;showAttachments=false&#38;showTranslations=false&#38;exclusions=document%3Axwiki%3ASandbox.WebHome&#38;exclusions=document%3Axwiki%3AHelp.WebHome&#38;exclusions=document%3Axwiki%3AMenu.WebHome&#38;exclusions=document%3Axwiki%3AXWiki.WebHome" data-dragAndDrop = "false" data-contextMenu = "false" data-icons = "false" data-edges = "false" data-checkboxes = "false" data-openTo = "document:xwiki:XWiki.XWikiPreferences" data-finder = "false" ></div></div></div> - </div> - - </div><div id="rightPanels" class="panels right panel-width-Medium"> - <div class="xwiki-async" data-xwiki-async-id="uix/xwiki%3AHelp.TipsPanel.WebHome/author/xwiki%3AXWiki.superadmin/locale/en/secureDocument/xwiki%3AHelp.TipsPanel.WebHome/9" data-xwiki-async-client-id="9"></div> - - <div class="panel expanded HelpSupportPanel WebHome"><h1 class="xwikipaneltitle">Need help?</h1><div class="xwikipanelcontents"><div class="SupportPanel"><p>If you need help with XWiki you can contact:</p><ul><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HCommunitySupport">Community Support</a></span></li><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HProfessionalSupport">Professional Support</a></span></li></ul></div></div></div> - </div> - -<div class="clearfloats"></div> - </div></div><div id="footerglobal"> - <div id="xwikilicence"></div> - <div id="xwikiplatformversion"> - <a href="http://extensions.xwiki.org?id=org.xwiki.platform:xwiki-platform-distribution-jetty-hsqldb:12.1-SNAPSHOT:::/xwiki-commons-pom/xwiki-platform/xwiki-platform-distribution/xwiki-platform-distribution-jetty-hsqldb"> - XWiki Jetty HSQLDB 12.1-SNAPSHOT - </a> - </div> - </div> - -</div></div></body> -</html> \ No newline at end of file diff --git a/src/test/resources/htmltests/xwiki-1324.html.gz b/src/test/resources/htmltests/xwiki-1324.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..56892daf8098041aec7c667977f72b5decf7cc4b GIT binary patch literal 11041 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 bS!Qj7t>Nb<uP|$iX?*Z+wlP#02-yGtSbB^L literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/xwiki-edit.html b/src/test/resources/htmltests/xwiki-edit.html deleted file mode 100644 index e5754fe277..0000000000 --- a/src/test/resources/htmltests/xwiki-edit.html +++ /dev/null @@ -1,1015 +0,0 @@ -<!DOCTYPE html> - <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" data-xwiki-reference="xwiki:XWiki.XWikiPreferences" data-xwiki-document="XWiki.XWikiPreferences" data-xwiki-wiki="xwiki" data-xwiki-space="XWiki" data-xwiki-page="XWikiPreferences" data-xwiki-isnew="false" data-xwiki-version="3.1" data-xwiki-rest-url="/xwiki/rest/wikis/xwiki/spaces/XWiki/pages/XWikiPreferences" data-xwiki-form-token="epgA3yUHjVWOMi0Zxgi4eQ" data-xwiki-user-reference="xwiki:XWiki.Admin" data-xwiki-locale=""> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> - <title>Global Administration - XWiki</title> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="shortcut icon" href="/xwiki/resources/icons/xwiki/favicon.ico?cache-version=1581617618000" /> - <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon16.png?cache-version=1581617618000" type="image/png" /> - <link rel="icon" href="/xwiki/resources/icons/xwiki/favicon.svg?cache-version=1581617618000" type="image/svg+xml" /> - <link rel="apple-touch-icon" href="/xwiki/resources/icons/xwiki/favicon144.png?cache-version=1581617618000" /> - <link rel="canonical" href="/xwiki/bin/view/XWiki/XWikiPreferences" /> - <meta name="revisit-after" content="7 days" /> -<meta name="description" content="Global Administration" /> -<meta name="keywords" content="wiki" /> -<meta name="rating" content="General" /> -<meta name="author" content="superadmin" /> -<link rel="alternate" type="application/rss+xml" title="Wiki Feed RSS" href="/xwiki/bin/view/Main/WebRss?xpage=rdf" /> -<link rel="alternate" type="application/rss+xml" title="Blog RSS Feed" href="/xwiki/bin/view/Blog/GlobalBlogRss?xpage=plain" /> - <link href="/xwiki/webjars/wiki%3Axwiki/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.min.css" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/xwiki-platform-tree-webjar/12.1-SNAPSHOT/tree.min.css?evaluate=true" type='text/css' rel='stylesheet'/><link href="/xwiki/webjars/wiki%3Axwiki/selectize.js/0.12.5/css/selectize.bootstrap3.css" type='text/css' rel='stylesheet'/> - <link href="/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/css/drawer.min.css" rel="stylesheet" type="text/css" /> - - - - - -<link href=" /xwiki/bin/skin/skins/flamingo/style.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg - " rel="stylesheet" type="text/css" media="all" /> -<link href=" /xwiki/bin/skin/skins/flamingo/print.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg - " rel="stylesheet" type="text/css" media="print" /> - <!--[if IE]> - <link href=" /xwiki/bin/skin/skins/flamingo/ie-all.css?cache-version=1581617618000&skin=XWiki.DefaultSkin&#38;colorTheme=xwiki%3AFlamingoThemes.Iceberg - " rel="stylesheet" type="text/css" /> -<![endif]--> - - <link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/css/xwiki-min.css?cache-version=1581618408000&colorTheme=FlamingoThemes.Iceberg&amp;language=en'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/widgets/validation/livevalidation.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/uicomponents/suggest/xwiki.selectize.css?cache-version=1581618406000'/><link rel='stylesheet' type='text/css' href='/xwiki/bin/skin/resources/js/xwiki/table/livetable.css?cache-version=1581618404000'/> - <link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Style?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Tour/HomepageTour/WebHome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/AnnotationCode/Settings?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminSheet?language=en&amp;docVersion=1.1&amp;colorTheme=FlamingoThemes.Iceberg" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Panels/Applications?language=en&amp;docVersion=1.1" /><link rel="stylesheet" type="text/css" href="/xwiki/bin/ssx/Help/SupportPanel/WebHome?language=en&amp;docVersion=1.1" /> - - <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/requirejs/2.3.6/require.min.js?r=1" - data-wysiwyg="true"></script> - - <script type='text/javascript' src='/xwiki/resources/js/prototype/prototype.js?cache-version=1581618402000'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/js/scriptaculous/effects.js?cache-version=1581618404000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/xwiki-min.js?cache-version=1581618408000&defer=false&amp;language=en'></script> -<script type='text/javascript' src='/xwiki/bin/skin/skins/flamingo/flamingo.min.js?cache-version=1581618398000&language=en' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/search/searchSuggest.js?cache-version=1581617618000&h=1255116547' defer='defer'></script> -<script type='text/javascript' src='/xwiki/resources/uicomponents/lock/lock.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/resources/uicomponents/widgets/validation/livevalidation_prototype.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/async/async.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/hierarchy/hierarchy.js?cache-version=1581618406000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/widgets/tree.min.js?cache-version=1581618398000' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/uicomponents/suggest/suggestUsersAndGroups.js?cache-version=1581618406000&language=en' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/table/livetable.js?cache-version=1581618404000' defer='defer'></script> - - <script type='text/javascript' src='/xwiki/bin/jsx/TourCode/TourJS?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Settings?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/AnnotationCode/Script?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/IconThemes/FontAwesome?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/Macro/NotificationsMacro?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/Notifications/Code/NotificationsDisplayerUIX?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/QuickSearchUIX?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminSheet?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/AdminGroupsSheet?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/XWiki/XWikiGroupSheet?language=en&amp;docVersion=1.1' defer='defer'></script> -<script type='text/javascript' src='/xwiki/bin/jsx/Panels/Applications?language=en&amp;docVersion=1.1' defer='defer'></script> - - -<script type="text/javascript" src="/xwiki/resources/js/xwiki/compatibility.js?cache-version=1581618404000" defer="defer"></script> -<script type="text/javascript" src="/xwiki/resources/js/xwiki/markerScript.js?cache-version=1581618404000" defer="defer"></script> -<!--[if lt IE 9]> - <script src="/xwiki/webjars/wiki%3Axwiki/html5shiv/3.7.2/html5shiv.min.js?r=1"></script> - <script type="text/javascript" src="/xwiki/webjars/wiki%3Axwiki/respond/1.4.2/dest/respond.min.js?r=1" defer="defer"></script> -<![endif]--> -<script type="text/javascript" data-wysiwyg="true"> -// <![CDATA[ -require.config({ - paths: { - 'jquery': '/xwiki/webjars/wiki%3Axwiki/jquery/2.2.4/jquery.min.js?r=1', - 'bootstrap': '/xwiki/webjars/wiki%3Axwiki/bootstrap/3.4.1/js/bootstrap.min.js?r=1', - 'xwiki-meta': '/xwiki/resources/js/xwiki/meta.js?cache-version=1581618404000', - 'xwiki-events-bridge': "/xwiki/resources/js/xwiki/eventsBridge.js?cache-version=1581618404000", - 'iscroll': '/xwiki/webjars/wiki%3Axwiki/iscroll/5.1.3/build/iscroll-lite.js?r=1', - 'drawer': '/xwiki/webjars/wiki%3Axwiki/drawer/2.4.0/js/jquery.drawer.min.js?r=1', - 'deferred': "/xwiki/resources/uicomponents/require/deferred.js?cache-version=1581618406000" - }, - shim: { - 'bootstrap' : ['jquery'], - 'drawer': ['jquery', 'iscroll'] - }, - map: { - '*': { - 'jquery': 'jQueryNoConflict' - }, - 'jQueryNoConflict': { - 'jquery': 'jquery' - }, - } -}); -define('jQueryNoConflict', ['jquery'], function ($) { - return $.noConflict(); -}); -if (window.Prototype && Prototype.BrowserFeatures.ElementExtensions) { - require(['jquery', 'bootstrap'], function ($) { - // Fix incompatibilities between BootStrap and Prototype - var disablePrototypeJS = function (method, pluginsToDisable) { - var handler = function (event) { - event.target[method] = undefined; - setTimeout(function () { - delete event.target[method]; - }, 0); - }; - pluginsToDisable.each(function (plugin) { - $(window).on(method + '.bs.' + plugin, handler); - }); - }, - pluginsToDisable = ['collapse', 'dropdown', 'modal', 'tooltip', 'tab', 'popover']; - disablePrototypeJS('show', pluginsToDisable); - disablePrototypeJS('hide', pluginsToDisable); - }); -} -require(['jquery', 'drawer'], function($) { - $(document).ready(function() { - $('.drawer-main').closest('body').drawer(); - }); -}); -window.XWiki = window.XWiki || {}; -XWiki.webapppath = "xwiki/"; -XWiki.servletpath = "bin/"; -XWiki.contextPath = "/xwiki"; -XWiki.mainWiki = "xwiki"; -// Deprecated: replaced by meta data in the HTML element -XWiki.currentWiki = "xwiki"; -XWiki.currentSpace = "XWiki"; -XWiki.currentPage = "XWikiPreferences"; -XWiki.editor = "globaladmin"; -XWiki.viewer = ""; -XWiki.contextaction = "admin"; -XWiki.skin = 'XWiki.DefaultSkin'; -XWiki.docisnew = false; -XWiki.docsyntax = "xwiki/2.0"; -XWiki.docvariant = ""; -XWiki.blacklistedSpaces = [ ]; -XWiki.hasEdit = true; -XWiki.hasProgramming = true; -XWiki.hasBackupPackImportRights = true; -XWiki.hasRenderer = true; -window.docviewurl = "/xwiki/bin/view/XWiki/XWikiPreferences"; -window.docediturl = "/xwiki/bin/edit/XWiki/XWikiPreferences"; -window.docsaveurl = "/xwiki/bin/save/XWiki/XWikiPreferences"; -window.docgeturl = "/xwiki/bin/get/XWiki/XWikiPreferences"; -// ]]> -</script> - - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> - <body id="body" class="skin-flamingo wiki-xwiki space-XWiki viewbody content panel-left-width-Medium panel-right-width-Medium drawer drawer-right drawer-close"> -<div id="xwikimaincontainer"> -<div id="xwikimaincontainerinner"> - - <div id="menuview"> - - - - - - - - - - - - - - - - - - - - - <nav class="navbar navbar-default actionmenu"> - <div class="container-fluid"> - <div class="navbar-header"> - <div id="companylogo"> - <a href="/xwiki/bin/view/Main/" title="Home" rel="home" class="navbar-brand"> - <img src="/xwiki/bin/download/FlamingoThemes/Iceberg/logo.svg?rev=1.1" alt="Wiki Logo"/> - </a> -</div> - - </div> - <div id="xwikimainmenu"> - - <ul class="nav navbar-nav navbar-left"> - <li class="divider" role="separator"></li> - </ul> - - <ul class="nav navbar-nav navbar-right"> - <li> - <a class="icon-navbar drawer-toggle" id="tmDrawerActivator" title="Drawer"><span class="sr-only">Toggle navigation</span><span class="fa fa-bars"></span></a> - </li> - <li class="navbar-avatar"> -<a href="/xwiki/bin/view/XWiki/Admin" class="icon-navbar"> -<span class="sr-only">User Profile</span> - <img class='avatar avatar_50' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/></a> -</li> - -<li class="dropdown" id="tmNotifications"> -<a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" -title="Notifications"> -<span class="sr-only"> -Toggle navigation -</span> -<span class="fa fa-bell"></span> -</a> -<ul class="dropdown-menu"> - <li> -<div class="notifications-header"> -<div class="clearfix"> -<div class="col-xs-4"> -<p><strong>Notifications</strong></p> -</div> -<div class="col-xs-8 text-right"> -<p> -<span class="notifications-header-link"> -<a href="/xwiki/bin/get/XWiki/Notifications/Code/NotificationRSSService?outputSyntax=plain" -class="notifications-header-link notifications-rss-link" rel="nofollow external"> -<span class="fa fa-rss"></span>&nbsp;RSS Feed -</a> -</span> -<span class="notifications-header-link"> -<a href="/xwiki/bin/view/XWiki/Admin?category=notifications" class="notifications-settings" rel="nofollow"> -<span class="fa fa-cog"></span>&nbsp;Settings -</a> -</span> -</p> -</div> -</div> -<div class="notifications-toggles clearfix" data-enabled="false"> -<pre> -<label class="hidden" for="notificationPageOnly">Toggle Page notifications only</label><input type="checkbox" id="notificationPageOnly" name="notificationPageOnly" checked="checked"/><label class="hidden" for="notificationPageAndChildren">Toggle Page and children notifications</label><input type="checkbox" id="notificationPageAndChildren" name="notificationPageAndChildren" checked="checked"/><label class="hidden" for="notificationWiki">Toggle Wiki notifications</label><input type="checkbox" id="notificationWiki" name="notificationWiki" checked="checked"/></pre> -</div> -<div class="notifications-header-uix col-xs-12"> -</div> -</div> -<div class="notifications-area loading clearfix"> -</div> -</li> - -</ul> -</li> - <li> -<form class="navbar-form globalsearch globalsearch-close form-inline" id="globalsearch" action="/xwiki/bin/view/Main/Search"> -<label class="hidden" for="headerglobalsearchinput">Search</label> -<input type="text" name="text" placeholder="search..." id="headerglobalsearchinput" /> -<button type="submit" class="btn" title="Search"><span class="fa fa-search"></span></button> -</form> -</li> - </ul> - - </div> </div> </nav> - - - - - - - -<div class="drawer-main drawer-default" id="tmDrawer"> - <nav class="drawer-nav"> - - <div class="drawer-brand clearfix"> - <a href="/xwiki/bin/view/XWiki/Admin"> - <img class='avatar avatar_120' src='/xwiki/bin/skin/resources/icons/xwiki/noavatar.png?cache-version=1581617618000' alt='Administrator' title='Administrator'/> </a> - <div class="brand-links"> - <a href="/xwiki/bin/view/XWiki/Admin" class="brand-user" id="tmUser">Administrator</a> - <a href="/xwiki/bin/logout/XWiki/XWikiLogout?xredirect=%2Fxwiki%2Fbin%2Fadmin%2FXWiki%2FXWikiPreferences%3Feditor%3Dglobaladmin%26section%3DGroups" id="tmLogout" rel="nofollow"><span class="fa fa-sign-out"></span> Log-out</a> - </div> - </div> - - <ul class="drawer-menu"> - <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Home</li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/admin/XWiki/XWikiPreferences" id="tmAdminWiki" rel="nofollow"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-wrench"></span> - </div> - <div class="drawer-menu-item-text">Administer Wiki</div> - </a> - </li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/Main/AllDocs" id="tmWikiDocumentIndex"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-book"></span> - </div> - <div class="drawer-menu-item-text">Page Index</div> - </a> - </li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/Main/UserDirectory" id="tmMainUserIndex"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-user"></span> - </div> - <div class="drawer-menu-item-text">User Index</div> - </a> - </li> - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/Applications/" id="tmMainApplicationIndex"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-th"></span> - </div> - <div class="drawer-menu-item-text">Application Index</div> - </a> - </li> - <li class="drawer-menu-item drawer-category-header"><hr class="hidden"/>Global</li> - - - - - - - - <li class="drawer-menu-item"> - <a href="/xwiki/bin/view/WikiManager/"> - <div class="drawer-menu-item-icon"> - <span class="fa fa-list-alt"></span> - </div> - <div class="drawer-menu-item-text">Wiki Index</div> - </a> - </li> - - </ul> - </nav> -</div> - - - - - - </div> - <div id="headerglobal"> - <div id="globallinks"> - </div> <div class="clearfloats"></div> - - - </div> - - -<div class="content" id="contentcontainer"> -<div id="contentcontainerinner"> -<div class="leftsidecolumns"> - <div id="contentcolumn"> - <div class="main layoutsubsection"> -<div id="mainContentArea"> - - - - - - - - - - - - - - - - - - - - - - - - - <ol class="breadcrumb breadcrumb-expandable" data-entities="{xwiki:XWiki.XWikiPreferences=XWiki.XWikiPreferences}" data-entity="XWiki.XWikiPreferences" data-id="hierarchy" data-limit="5" data-treenavigation="true" id="hierarchy"><li class="wiki dropdown"><a href="/xwiki/bin/view/Main/"><span class="fa fa-home"></span></a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10"></div> -</div></li><li class="space dropdown"><a href="/xwiki/bin/view/XWiki/">XWiki</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.WebHome" data-responsive="true" data-root="{&quot;type&quot;:&quot;wiki&quot;,&quot;id&quot;:&quot;xwiki&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=wiki%3Axwiki"></div> -</div></li><li class="document active dropdown"><a href="/xwiki/bin/view/XWiki/XWikiPreferences">Global Administration</a><span class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-caret-down"></span></span><div class="dropdown-menu"> <div class="breadcrumb-tree" data-checkboxes="false" data-contextmenu="false" data-draganddrop="false" data-edges="true" data-finder="false" data-icons="true" data-opento="document:xwiki:XWiki.XWikiPreferences" data-responsive="true" data-root="{&quot;type&quot;:&quot;document&quot;,&quot;id&quot;:&quot;xwiki:XWiki.WebHome&quot;}" data-url="/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&amp;sheet=XWiki.DocumentTree&amp;showTranslations=false&amp;limit=10&amp;root=document%3Axwiki%3AXWiki.WebHome"></div> -</div> </li></ol> - <div id="document-title"><h1 id="HGlobalAdministration:Groups" class="wikigeneratedid wikigeneratedheader"><span>Global Administration: Groups</span></h1><div class="noitems"><p>Manage user groups: add or remove groups, or change group members.</p></div><hr/></div><div id="administration-menu" class="panel-group admin-menu" role="tablist" aria-multiselectable="true"> -<div class="panel xform"> -<label for="adminsearchmenu" class="hidden">Search</label> -<input type="text" class="form-control panel-group-filter" autocomplete="off" id="adminsearchmenu" -placeholder="Search for..." /> -</div> - <div class="panel panel-default"> -<a class="panel-heading" role="tab" id="panel-heading-usersgroups" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=0" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-usersgroups" aria-expanded="true" aria-controls="panel-body-usersgroups" -title="Manage users, groups, and their access rights."><span class="fa fa-group"></span>Users &#38; Rights</a> -<div class="panel-collapse collapse in" role="tabpanel" id="panel-body-usersgroups" -aria-labelledby="panel-heading-usersgroups"> -<div class="list-group"> -<a class="list-group-item" data-id="Users" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Users" title="Manage users of this wiki: add, remove, modify their profile information." ->Users</a> -<a class="list-group-item active" data-id="Groups" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Groups" title="Manage user groups: add or remove groups, or change group members." ->Groups</a> -<a class="list-group-item" data-id="Rights" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Rights" title="Manage groups and users rights: control who can view, edit and delete pages." ->Rights</a> -<a class="list-group-item" data-id="UserProfile" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=UserProfile" title="Manage what information is displayed on the user profile of each user." ->User Profile</a> -<a class="list-group-item" data-id="userdirectory" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&RIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHERERIGHTHEREsection=userdirectory" title="Customize the user directory live table." ->User Directory</a> -<a class="list-group-item" data-id="Registration" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Registration" title="Manage user registration settings." ->Registration</a> -<a class="list-group-item" data-id="Invitation" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Invitation" title="Configure the Invitation Application" ->Invitation</a> -<a class="list-group-item" data-id="Authentication" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Authentication" title="" ->Authentication</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-extensionmanager" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=1" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-extensionmanager" aria-expanded="false" aria-controls="panel-body-extensionmanager" -title="Search, add, upgrade and remove extensions."><span class="fa fa-puzzle-piece"></span>Extensions</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-extensionmanager" -aria-labelledby="panel-heading-extensionmanager"> -<div class="list-group"> -<a class="list-group-item" data-id="XWiki.Extensions" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.Extensions" title="Search for new extensions to add to the wiki." ->Extensions</a> -<a class="list-group-item" data-id="XWiki.ExtensionHistory" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionHistory" title="See the history of the installed extensions." ->History</a> -<a class="list-group-item" data-id="XWiki.ExtensionUpdater" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.ExtensionUpdater" title="Check if there are any updates available for the installed extensions." ->Updater</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-lf" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=2" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-lf" aria-expanded="false" aria-controls="panel-body-lf" -title="Change the aspect and layout of the wiki."><span class="fa fa-columns"></span>Look &#38; Feel</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-lf" -aria-labelledby="panel-heading-lf"> -<div class="list-group"> -<a class="list-group-item" data-id="Themes" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Themes" title="Customize the color and icon themes, skin and logo." ->Themes</a> -<a class="list-group-item" data-id="menu.name" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=menu.name" title="" ->Menus</a> -<a class="list-group-item" data-id="Panels.PanelWizard" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Panels.PanelWizard" title="Add and remove panels, change the page layout." ->Panels</a> -<a class="list-group-item" data-id="panels.applications" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.applications" title="Manage what applications are displayed in the Applications Panel." ->Applications Panel</a> -<a class="list-group-item" data-id="panels.navigation" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=panels.navigation" title="Manage what pages are displayed in the Navigation Panel." ->Navigation Panel</a> -<a class="list-group-item" data-id="Presentation" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Presentation" title="Choose the page tabs that are visible and configure the page header and footer." ->Presentation</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-content" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=3" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-content" aria-expanded="false" aria-controls="panel-body-content" -title="Manipulate the content of the wiki."><span class="fa fa-file-text-o"></span>Content</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-content" -aria-labelledby="panel-heading-content"> -<div class="list-group"> -<a class="list-group-item" data-id="Templates" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Templates" title="Settings for the creation of page templates." ->Page Templates</a> -<a class="list-group-item" data-id="Localization" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Localization" title="Language-related settings." ->Localization</a> -<a class="list-group-item" data-id="Import" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Import" title="Import pages or applications into the wiki." ->Import</a> -<a class="list-group-item" data-id="Export" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Export" title="Export wiki pages into a XAR." ->Export</a> -<a class="list-group-item" data-id="Annotations" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Annotations" title="Configure the page annotations" ->Annotations</a> -<a class="list-group-item" data-id="XWiki.OfficeImporterAdmin" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=XWiki.OfficeImporterAdmin" title="Configure the Office Server." ->Office Server</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-edit" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=4" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-edit" aria-expanded="false" aria-controls="panel-body-edit" -title="Configure the edit mode, the WYSIWYG editor and the available syntaxes."><span class="fa fa-pencil"></span>Editing</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-edit" -aria-labelledby="panel-heading-edit"> -<div class="list-group"> -<a class="list-group-item" data-id="Editing" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Editing" title="Choose the default edit mode and configure its title and versioning parameters." ->Edit Mode</a> -<a class="list-group-item" data-id="WYSIWYG" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=WYSIWYG" title="Choose the default WYSIWYG editor and configure it." ->WYSIWYG Editor</a> -<a class="list-group-item" data-id="Syntaxes" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Syntaxes" title="Choose the default page syntax and configure the available markup syntaxes." ->Syntaxes</a> -<a class="list-group-item" data-id="Name Strategies" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Name%20Strategies" title="" ->Name Strategies</a> -<a class="list-group-item" data-id="SyntaxHighlighting" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=SyntaxHighlighting" title="" ->Syntax Highlighting</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-email" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=5" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-email" aria-expanded="false" aria-controls="panel-body-email" -title="Configure mail sending parameters and view mail statuses."><span class="fa fa-envelope-o"></span>Mail</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-email" -aria-labelledby="panel-heading-email"> -<div class="list-group"> -<a class="list-group-item" data-id="emailSend" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailSend" title="Configure mail sending parameters." ->Mail Sending</a> -<a class="list-group-item" data-id="emailStatus" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailStatus" title="View the statuses of sent mails and resend mails that resulted in failure." ->Mail Sending Status</a> -<a class="list-group-item" data-id="emailGeneral" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=emailGeneral" title="Configure advanced mail parameters, like mail address obfuscation." ->Advanced</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-search" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=6" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-search" aria-expanded="false" aria-controls="panel-body-search" -title="Choose the default search engine or configure the search index."><span class="fa fa-search"></span>Search</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-search" -aria-labelledby="panel-heading-search"> -<div class="list-group"> -<a class="list-group-item" data-id="Search" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Search" title="Choose the default search engine or configure the search index." ->Search</a> -<a class="list-group-item" data-id="searchSuggest" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=searchSuggest" title="Configure the search suggest options." ->Search Suggest</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-wikis" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=7" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-wikis" aria-expanded="false" aria-controls="panel-body-wikis" -title="Wikis management."><span class="fa fa-globe"></span>Wikis</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-wikis" -aria-labelledby="panel-heading-wikis"> -<div class="list-group"> -<a class="list-group-item" data-id="wikis.descriptor" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.descriptor" title="Configure the wiki descriptor" ->Descriptor</a> -<a class="list-group-item" data-id="wikis.templates" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.templates" title="Manage the wiki templates" ->Wiki Templates</a> -<a class="list-group-item" data-id="wikis.rights" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=wikis.rights" title="" ->Creation Right</a> -</div> -</div> -</div> - <div class="panel panel-default"> -<a class="panel-heading collapsed" role="tab" id="panel-heading-other" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?category=8" data-toggle="collapse" data-parent="#administration-menu" data-target="#panel-body-other" aria-expanded="false" aria-controls="panel-body-other" -title="Various configurations for extensions."><span class="fa fa-wrench"></span>Other</a> -<div class="panel-collapse collapse" role="tabpanel" id="panel-body-other" -aria-labelledby="panel-heading-other"> -<div class="list-group"> -<a class="list-group-item" data-id="Notifications" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Notifications" title="" ->Notifications</a> -<a class="list-group-item" data-id="Logging" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=Logging" title="Review and modify the log level associated to a registered logger." ->Logging</a> -<a class="list-group-item" data-id="captcha" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=captcha" title="" ->CAPTCHA</a> -<a class="list-group-item" data-id="analytics" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=analytics" title="Configure the Google Analytics™ account." ->Google Analytics™</a> -<a class="list-group-item" data-id="MessageStream" -href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&section=MessageStream" title="Enable or disable the message stream in the wiki." ->Message Stream</a> -</div> -</div> -</div> -<div class="panel panel-default noitems hidden"> -<div class="panel-heading collapsed"> -No results. -</div> -</div> -</div><div id="admin-page-content"> - <div class="medium-avatars"> - <div class="xwiki-livetable-container"> - <table id="groupstable" class="xwiki-livetable"> - <tr> - <td class="xwiki-livetable-pagination"> - <span id="groupstable-limits" class="xwiki-livetable-limits"></span> - <span id="groupstable-pagesize" class="xwiki-livetable-pagesize"> - <span>per page of</span> - <span class="xwiki-livetable-pagesize-content" ></span> - </span> - <span id="groupstable-ajax-loader" class="xwiki-livetable-loader hidden"> - <img src="/xwiki/resources/icons/xwiki/ajax-loader-large.gif?cache-version=1581617618000" alt="Loading..." title="" /> - </span> - <span class="controlPagination"> - <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> - <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> - </span> - <span class="pagination"> - <span class="xwiki-livetable-pagination-text">Page</span> - <span class="xwiki-livetable-pagination-content" ></span> - </span> - </td> - </tr> - <tr> - <td class="xwiki-livetable-display-container"> - <table class="xwiki-livetable-display"> - <thead class="xwiki-livetable-display-header"> - <tr> - <th scope="col" class="xwiki-livetable-display-header-text - "> - <label for="xwiki-livetable-groupstable-filter-1"> Group Name - </label> </th> - <th scope="col" class="xwiki-livetable-display-header-text - "> - Members - </th> - <th scope="col" class="xwiki-livetable-display-header-text actions - "> - Actions - </th> - </tr> - <tr class="xwiki-livetable-display-filters"> - <td class="xwiki-livetable-display-header-filter"> - <input id="xwiki-livetable-groupstable-filter-1" name="name" type="text" - title="Filter for the Group Name column" /> - </td> - <td class="xwiki-livetable-display-header-filter"> - </td> - <td class="xwiki-livetable-display-header-filter"> - </td> - </tr> - <tr class="xwiki-livetable-initial-message"> - <td colspan="3"> - <div class="warningmessage">The environment prevents the table from loading data.</div> - </td> - </tr> - </thead> - <tbody id="groupstable-display" class="xwiki-livetable-display-body"><tr><td>&nbsp;</td></tr></tbody> - </table> - </td> - </tr> - <tr> - <td class="xwiki-livetable-pagination"> - <span class="xwiki-livetable-limits"></span> - <span class="controlPagination"> - <a title="Previous Page" class="prevPagination" href="#"><span class="hidden">Previous Page</span></a> - <a title="Next Page" class="nextPagination" href="#"><span class="hidden">Next Page</span></a> - </span> - <span class="pagination"> - <span class="xwiki-livetable-pagination-text">Page</span> - <span class="xwiki-livetable-pagination-content" ></span> - </span> - </td> - </tr> - </table> - <div id="groupstable-inaccessible-docs" class="hidden"> - <div class="infomessage">(*) Some pages require special rights to be viewed.</div> - </div> - <div id="groupstable-computed-title-docs" class="hidden"> - <div class="infomessage">(<span class='docTitleComputed'></span>)&nbsp;Some pages have a computed title. Filtering and sorting by title will not work as expected for these pages.</div> - </div> - <script type="text/javascript"> - //<![CDATA[ -(function() { - var startup = function(container) { - // Make sure the LiveTable code is loaded (the WYSIWYG editor doesn't load the JavaScript code for instance). - var liveTableCodeLoaded = XWiki && XWiki.widgets && XWiki.widgets.LiveTable; - // Also make sure the live table is not already initialized. - var liveTableElement = $('groupstable'); - if (liveTableCodeLoaded && liveTableElement && !liveTableElement.__liveTable && - (liveTableElement == container || liveTableElement.descendantOf(container))) { - window["livetable_groupstable"] = liveTableElement.__liveTable = new XWiki.widgets.LiveTable("/xwiki/bin/view/XWiki/XWikiPreferences?xpage=getgroups", - "groupstable", function (row, i, table) { - // This callback method has been generated from Velocity. - var columns = ["name","members","_actions"]; - var columnDescriptors = {"name":{"type":"text","html":true,"sortable":false,"headerClass":null,"displayName":"Group Name"},"members":{"filterable":false,"sortable":false,"headerClass":null,"displayName":"Members"},"scope":{"type":"list","sortable":false,"displayName":"Scope"},"_actions":{"actions":[{"id":"edit","label":"edit","async":null,"callback":null,"icon":"<span class=\"fa fa-pencil\"></span>"},{"id":"delete","label":"delete","async":null,"callback":null,"icon":"<span class=\"fa fa-times\"></span>"}],"labels":{"delete":"delete"},"filterable":false,"headerClass":"actions","displayName":"Actions"}}; - var className = ""; - var showFilterNote = false; - if (!row['doc_viewable']) { - $("groupstable-inaccessible-docs").removeClassName('hidden'); - } - var tr = new Element('tr'); - columns.forEach(function(column) { - var descriptor = columnDescriptors[column] || {}; - if (descriptor.type === 'hidden') { - return; - } - // The column's display name to be used when displaying the reponsive version. - var displayName = descriptor.displayName || column; - var fieldName = column.replace(/^doc\./, 'doc_'); - if (column === '_actions') { - var adminActions = ['admin', 'rename', 'rights']; - var td = new Element('td', { - 'class': 'actions', - 'data-title': displayName - }); - td.toggleClassName('hide-labels', descriptor.labels === false); - (descriptor.actions || []).forEach(function(action, index) { - if (row['doc_has' + action.id] || action.id === 'view' || (row['doc_has' + action.id] === undefined && - (row['doc_hasadmin'] || adminActions.indexOf(action.id) < 0))) { - var link = new Element('a', { - 'href': row['doc_' + action.id + '_url'] || row['doc_url'], - 'class': 'action action' + action.id - }).update('<span class="action-icon"></span><span class="action-label"></span>'); - link.down('.action-icon').update(action.icon).writeAttribute('title', action.label); - link.down('.action-label').update(action.label.escapeHTML()); - if (action.async) { - link.observe('click', function(event) { - event.stop(); - new Ajax.Request(this.href, { - onSuccess: function() { - eval(action.callback); - } - }); - }.bindAsEventListener(link)); - } - td.insert(link); - } - }); - tr.appendChild(td); - } else { - var td = new Element('td', { - 'class': [ - fieldName, - 'link' + (descriptor.link || ''), - 'type' + (descriptor.type || '') - ].join(' '), - 'data-title': displayName - }); - var container = td; - if (descriptor.link && row['doc_viewable']) { - var link = new Element(descriptor.link === 'editor' ? 'span' : 'a'); - // Automatic: the link URL is in JSON results, with the '_url' sufix. - if (descriptor.link === 'auto') { - link.href = row[fieldName + '_url'] || row['doc_url']; - } else if (descriptor.link === 'field') { - if (row[fieldName + '_url']) { - link.href = row[fieldName + '_url']; - } - // Property editor - } else if (descriptor.link === 'editor') { - var propertyClassName = descriptor['class'] || className; - td.observe('click', function(event) { - var tag = event.element().down('span') || event.element(); - editProperty(row['doc_fullName'], propertyClassName, column, function(value) { - tag.innerHTML = value; - }); - }); - // Author, space or wiki link. - } else if (row['doc_' + descriptor.link + '_url']) { - link.href = row['doc_' + descriptor.link + '_url']; - } else { - link.href = row['doc_url']; - } - td.appendChild(link); - container = link; - } - // The value can be passed as a string.. - if (descriptor.html + '' === 'true') { - container.innerHTML = row[fieldName] || ''; - } else if (row[fieldName] !== undefined && row[fieldName] !== null) { - var text = row[fieldName] + ''; - if (fieldName === 'doc_name' && !row['doc_viewable']) { - text += '*'; - } - if (showFilterNote && fieldName === 'doc_title' && row['doc_title_raw'] !== undefined) { - container.addClassName('docTitleComputed'); - } - container.update(text.escapeHTML()); - } - tr.appendChild(td); - } - }); - return tr; -} -, {"maxPages":10,"limit":15,"selectedTags":[]}); - document.observe("xwiki:livetable:groupstable:loadingEntries", function() { - $('groupstable-pagesize').addClassName("hidden"); - }); - document.observe("xwiki:livetable:groupstable:loadingComplete", function() { - $('groupstable-pagesize').removeClassName("hidden"); - }); - return true; - } - return false; - }; - var init = function(event) { - var elements = (event && event.memo.elements) || [$('body')]; - return elements.length > 0 && elements.some(startup); - }; - // Initialize the live table on page load or after the live table code has been loaded. - (XWiki && XWiki.isInitialized && init()) || document.observe('xwiki:livetable:loading', init); - // Initialize the live table when it is added after the page is loaded. - document.observe('xwiki:dom:updated', init); -})(); - //]]> - </script> -</div></div> -<p> -<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#createGroupModal"> -Create group -</button> -</p> -<div class="modal" id="createGroupModal" tabindex="-1" role="dialog" -aria-labelledby="createGroupModal-label" data-backdrop="static" data-keyboard="false"> -<div class="modal-dialog" role="document"> -<form class="modal-content xform"> -<div class="modal-header"> -<button type="button" class="close" data-dismiss="modal" aria-label="Close"> -<span aria-hidden="true">&times;</span> -</button> -<div class="modal-title" id="createGroupModal-label"> -Create group -</div> -</div> -<div class="modal-body"> -<div class="hidden"> -<input type="hidden" name="form_token" value="epgA3yUHjVWOMi0Zxgi4eQ" /> -<input type="hidden" name="template" value="XWiki.XWikiGroupTemplate" /> -</div> -<dl> -<dt> -<label for="createGroupModal-groupName" class="sr-only"> -Group Name -</label> -</dt> -<dd class="form-group has-feedback"> -<input type="text" class="form-control" id="createGroupModal-groupName" name="name" autocomplete="off" -placeholder="Group Name" /> -<span class="form-control-feedback loading hidden" aria-hidden="true"></span> -<span class="form-control-feedback success hidden" aria-hidden="true"><span class="fa fa-check"></span></span> -<span class="form-control-feedback error hidden" aria-hidden="true"><span class="fa fa-times"></span></span> -<span class="help-block hidden"></span> -</dd> -</dl> -</div> -<div class="modal-footer"> -<button type="button" class="btn btn-default" data-dismiss="modal"> -Cancel -</button> -<button type="submit" class="btn btn-primary"> -Create -</button> -</div> -</form> -</div> -</div> - -<div class="modal" id="editGroupModal" tabindex="-1" role="dialog" aria-labelledby="editGroupModal-label" -data-backdrop="static" data-keyboard="false" data-liveTable="#groupstable" data-liveTableAction="edit"> -<div class="modal-dialog" role="document"> -<div class="modal-content"> -<div class="modal-header"> -<button type="button" class="close" data-dismiss="modal" aria-label="Close"> -<span aria-hidden="true">&times;</span> -</button> -<div class="modal-title" id="editGroupModal-label"> -Edit group -</div> -</div> -<div class="modal-body"></div> -</div> -</div> -</div> - -<div class="modal" id="deleteGroupModal" tabindex="-1" role="dialog" aria-labelledby="deleteGroupModal-label" -data-liveTable="#groupstable" data-liveTableAction="delete"> -<div class="modal-dialog" role="document"> -<div class="modal-content"> -<div class="modal-header"> -<button type="button" class="close" data-dismiss="modal" aria-label="Close"> -<span aria-hidden="true">&times;</span> -</button> -<div class="modal-title" id="deleteGroupModal-label"> -Delete group -</div> -</div> -<div class="modal-body"> -<p>The group <span class="groupName"></span> will be deleted. Are you sure you want to proceed?</p> -</div> -<div class="modal-footer"> -<button type="button" class="btn btn-default" data-dismiss="modal"> -Cancel -</button> -<button type="submit" class="btn btn-danger" data-dismiss="modal"> -Delete -</button> -</div> -</div> -</div> -</div> -</div> - <div class="clearfloats"></div> -</div></div></div><div id="leftPanels" class="panels left panel-width-Medium"> - <div class="panel expanded PanelsApplications Applications"><h1 class="xwikipaneltitle">Applications</h1><div class="xwikipanelcontents"><ul class="applicationsPanel nav nav-pills nav-stacked"> -<li> -<a href="/xwiki/bin/view/Dashboard/" title="Dashboard"> -<span class="application-img"><span class="fa fa-th-large"></span></span> -<span class="application-label">Dashboard</span> -</a> -</li> -<li> -<a href="/xwiki/bin/view/Help/" title="Help"> -<span class="application-img"><span class="fa fa-question-circle"></span></span> -<span class="application-label">Help</span> -</a> -</li> -<li> -<a href="/xwiki/bin/view/Sandbox/" title="Sandbox"> -<span class="application-img"><span class="fa fa-coffee"></span></span> -<span class="application-label">Sandbox</span> -</a> -</li> -</ul> -<ul class="applicationsPanel applicationsPanelMoreList nav nav-pills nav-stacked"> -<li> -<a class="applicationPanelMoreButton" href="/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.AddExtensions" title="More applications"> -<span class="application-img"><span class="fa fa-plus"></span></span> -<span class="application-label">More applications</span> -</a> -<div class="applicationPanelMoreContainer hidden"> -<ul class="nav nav-pills nav-stacked"> -<li> -<a href="/xwiki/bin/view/AppWithinMinutes/" title="Create your own!"> -<span class="application-img"><span class="fa fa-caret-right"></span></span> -<span class="application-label">Create your own!</span> -</a> -</li> -<li> -<a href="/xwiki/bin/view/XWiki/XWikiPreferences?editor=globaladmin&amp;section=XWiki.Extensions" title="Install new applications"> -<span class="application-img"><span class="fa fa-caret-right"></span></span> -<span class="application-label">Install new applications</span> -</a> -</li> -</ul> -</div> -</li> -</ul></div></div> - <div class="panel expanded PanelsNavigation Navigation"><h1 class="xwikipaneltitle">Navigation</h1><div class="xwikipanelcontents"> - - - - - - - - <div class = "xtree" data-responsive = "true" data-url = "/xwiki/bin/get/XWiki/XWikiPreferences?outputSyntax=plain&#38;sheet=XWiki.DocumentTree&#38;showAttachments=false&#38;showTranslations=false&#38;exclusions=document%3Axwiki%3ASandbox.WebHome&#38;exclusions=document%3Axwiki%3AHelp.WebHome&#38;exclusions=document%3Axwiki%3AMenu.WebHome&#38;exclusions=document%3Axwiki%3AXWiki.WebHome" data-dragAndDrop = "false" data-contextMenu = "false" data-icons = "false" data-edges = "false" data-checkboxes = "false" data-openTo = "document:xwiki:XWiki.XWikiPreferences" data-finder = "false" ></div></div></div> - </div> - - </div><div id="rightPanels" class="panels right panel-width-Medium"> - <div class="xwiki-async" data-xwiki-async-id="uix/xwiki%3AHelp.TipsPanel.WebHome/author/xwiki%3AXWiki.superadmin/locale/en/secureDocument/xwiki%3AHelp.TipsPanel.WebHome/9" data-xwiki-async-client-id="9"></div> - - <div class="panel expanded HelpSupportPanel WebHome"><h1 class="xwikipaneltitle">Need help?</h1><div class="xwikipanelcontents"><div class="SupportPanel"><p>If you need help with XWiki you can contact:</p><ul><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HCommunitySupport">Community Support</a></span></li><li><span class="wikiexternallink"><a href="http://www.xwiki.org/xwiki/bin/view/Main/Support#HProfessionalSupport">Professional Support</a></span></li></ul></div></div></div> - </div> - -<div class="clearfloats"></div> - </div></div><div id="footerglobal"> - <div id="xwikilicence"></div> - <div id="xwikiplatformversion"> - <a href="http://extensions.xwiki.org?id=org.xwiki.platform:xwiki-platform-distribution-jetty-hsqldb:12.1-SNAPSHOT:::/xwiki-commons-pom/xwiki-platform/xwiki-platform-distribution/xwiki-platform-distribution-jetty-hsqldb"> - XWiki Jetty HSQLDB 12.1-SNAPSHOT - </a> - </div> - </div> - -</div></div></body> -</html> \ No newline at end of file diff --git a/src/test/resources/htmltests/xwiki-edit.html.gz b/src/test/resources/htmltests/xwiki-edit.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..82040c4aba8dd905ddf9f7720b73416bbae43a6f GIT binary patch literal 13934 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 X7^rHTi{dRjbADR0t*cgh?XUj_Y41R` literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/yahoo-article-1.html b/src/test/resources/htmltests/yahoo-article-1.html deleted file mode 100644 index c2302b7be3..0000000000 --- a/src/test/resources/htmltests/yahoo-article-1.html +++ /dev/null @@ -1,2043 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> -<html lang="en-US"> -<head> - <meta http-equiv="content-type" content="text/html; charset=UTF-8"> - -<link rel="stylesheet" type="text/css" href="http://l.yimg.com/d/combo?yui/2.7.0/build/fonts/fonts-min.css&yui/2.7.0/build/reset/reset-min.css&yui/2.7.0/build/grids/grids-min.css&media/phugc/mwphcom_r141.css&uh/15/css/uh_rsa-1.0.5.css&news/p/common/generic/common_rollup-min-38211.css&s5/miniassist_200912081429.css&media/m/location_widget/location_widget-min-22834.css&yui/2.7.0/build/container/assets/skins/sam/container.css&news/p/common/generic/widgets-min-10270.css&news/p/common/generic/comments-min-36358.css&news/p/story/generic/story-min-42898.css&media/m/social_buttons/social-buttons-min-3585.css" /> - - <link rel="canonical" href="http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china"></link> - - - <script type="text/javascript"> - // variable set from common function - var YNEWS_EdPick = '1'; - </script> - - - <meta http-equiv="Pragma" content="no-cache"> - <meta http-equiv="Expires" content="0"> - <meta property="fb:admins" content="100000971447717" /> - - <meta name="title" content="GM expects competitive Chevy Volt pricing in China" /> - <meta name="description" content="General Motors expects competitive pricing for its electric Chevrolet Volt in China as it hopes to gain a foothold in China&#39;s fledgling environmentally friendly car industry with the highly anticipated car."> - <meta name="keywords" content="Business,Reuters"> - <link rel="image_src" href="http://d.yimg.com/a/p/rids/20100831/t/r3693855494.jpg?x=103&y=130&xc=1&yc=1&wc=103&hc=130&q=85&sig=rdBzTk4xoV1.NA9FiEfTLA--" /> - - <title>GM expects competitive Chevy Volt pricing in China - Yahoo! News</title> - - - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> - -<body> - - <script type="text/javascript">document.body.className = 'js';</script> - - <div id="doc4" class="yui-t6"> - - <a href="#navigation" class="shortcut">Skip to navigation &raquo;</a> - <a href="#bd" class="shortcut">Skip to content &raquo;</a> - - <div id="hd"> - - <div class="mod ad mod-first ad_header" role="banner"> - <div class="bd"> - <!-- Ad Keywords: it; business; price; yuan; government; Yuan;--> -<style type="text/css">#ygma #ygmapromo a{color:#0058A6;}#ygma .sp{background-image:url(http://l.yimg.com/a/lib/uh/15/sprites/news-1.0.0.png);}#ygma .ygmacobrand{position:absolute;color:#666;font-size:90%;*font-size:80%;top:5.6em;*top:5.7em;_top:5.7em;bottom:0;margin-left:115px;*margin-left:-115px;}#ygma .ygmacobrand a:link,#ygma .ygmacobrand a:active,#ygma .ygmacobrand a:visited{color:#666;text-decoration:none;}#ygma .ygmacobrand a:hover{text-decoration:underline;}#ygmasrchbtn{line-height:inherit;}</style><script type="text/javascript">(function(){var h={};var setUp=function(){h = YAHOO.one.uh.hotlistInfo = {rd:"http://us.ard.yahoo.com",space:"/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=0",adid:"5877298",prop:"news",protocol:"http",host:"real-fe.story.news.yahoo.com:80",url:"%2fnews%2fstory%2fmaple%2fen-US%2fnm%2f20100831%2fbs_nm%2fus_gm_china",spaceid:"2023881070"};YAHOO.one.uh.translate=function(str){var set={yahoo_homepage:"http://www.yahoo.com/", hp_detect_script:"http://www.yahoo.com/includes/hdhpdetect.php", set_hp_script:"http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=1/SIG=10uacnjgh/*http://www.yahoo.com/bin/set", set_hp_firefox_instructions:["Drag the \"Y!\" and drop it onto the \"Home\" icon.", "Select \"Yes\" from the pop up window.", "Nothing, you're done."], close_this_window:"Close this window", set_hp_alternative_instructions1:"If this didn't work for you see ", detailed_set_hp_instructions:"detailed instructions", set_hp_alternative_instructions2:""};return set[str];};YAHOO.one.uh.Search=['ygmasearchInput', 'sat'];};if("undefined" !==typeof YAHOO && "undefined" !==typeof YAHOO.one && "undefined" !==typeof YAHOO.one.uh){setUp();}else{setTimeout(arguments.callee, 500);}})();</script><div id="ygma"><div id="ygmaheader"><div class="bd sp"><div id="ymenu" class="ygmaclr"><div id="mepanel"><ul id="mepanel-nav"><li class="me1"><em>New User? <a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=2/SIG=12q94pt2i;_ylt=A2KIKvTWiH1Ml3sBoAJu.aF4;_ylu=X3oDMTB0aTMyNzhrBHBvcwMxBHNlYwNoZWFkZXIEc2xrA3JlZ2lzdGVy/*https://edit.yahoo.com/config/eval_register?.done=http://news.yahoo.com&.src=yn&.intl=us" class="ygmasignup">Register</a></em></li><li class="me2"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=3/SIG=12ji5gcev;_ylt=A2KIKvTWiH1Ml3sBoQJu.aF4;_ylu=X3oDMTByb2s2MmRkBHBvcwMyBHNlYwNoZWFkZXIEc2xrA3NpZ25pbg--/*https://login.yahoo.com/config/login?.done=http://news.yahoo.com&.src=yn&.intl=us" ><em>Sign In</em></a></li><li class="me3"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=4/SIG=1184ggc4j;_ylt=A2KIKvTWiH1Ml3sBogJu.aF4;_ylu=X3oDMTBwNG9zOWhmBHBvcwMzBHNlYwNoZWFkZXIEc2xrA2hlbHA-/*http://help.yahoo.com/l/us/yahoo/news/" target="_top">Help</a></li></ul></div><div id="ygmapromo"><div id="ygmapromo-i"></div> -<style> -#ygma-s{display:none;} -#ygma #ygmapromo-i, #ygmabt #ygmapromo-i {display:inline;} -</style> -<script> -(function(){ -window.YAHOO = window.YAHOO || {}; -YAHOO.one = window.YAHOO.one || {}; -YAHOO.one.uh = window.YAHOO.one.uh || {}; -YAHOO.one.uh.popularSearches = function(data) { - var chop = function(s, n) { - if (s.length > n) { - return s.substring(0,n)+"..."; - } - else return s; - } - var e = document.getElementById("ygmapromo") || document.getElementById("ygmabtpromo"); - var e2 = document.getElementById("ygmapromo-i"); - var i = data.query.results.links; - var hd = i.text; - var fr = "&fr=ush-tts&fr2=ps"; - e2.innerHTML = '<a href="http://us.ard.yahoo.com/SIG=15r1vctu9/M=650008.13959238.14033549.12832737/D=news/S=2023881070:HPRM2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=CmJLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=6076890/R=0;_ylt=A2KIKvTWiH1Ml3sBowJu.aF4;_ylu=X3oDMTExOHVpaGkwBHBvcwM0BHNlYwNoZWFkZXIEc2xrA3RyZW5kaW5nY2hvcA--/*'+i.href+fr+'" id="ygma-s-h">Trending: '+chop(hd, 20)+'</a>'; -}; -var ie; -if (navigator.userAgent.match(/MSIE\s6/)) { - var D = new Date(); - var yr = D.getFullYear(); - var mt = D.getMonth()+1; - var dy = D.getDate(); - var hr = D.getHours(); - ie = '&cache=' + yr + mt + dy + hr; -} -else { - ie = ""; -} -var y = document.getElementById("ygma") || document.getElementById("ygmabt"); -var feed; -if (y.offsetWidth < 850){ - feed = "http://query.yahooapis.com/v1/public/yql/uhTrending/cokeTrending3?format=json&callback=YAHOO.one.uh.popularSearches&_maxage=1800&diagnostics=false&limit=1"; -} -else { - feed = "http://query.yahooapis.com/v1/public/yql/uhTrending/cokeTrending2?format=json&callback=YAHOO.one.uh.popularSearches&_maxage=1800&diagnostics=false&limit=1"; -} -var h = document.getElementsByTagName("head").item(0); -var s = document.createElement("script"); -s.setAttribute("type", "text/javascript"); -s.setAttribute("charset", "utf-8"); -s.setAttribute("src", feed + ie); -h.appendChild(s); -})(); -</script> -<style> -#ygma ol.searches h4, #ygmabt ol.searches h4 { - font-size:100%; - font-weight:bold; - color:#333333; - text-align:left; - padding-bottom:3px; - margin:0; -} -#ygma ol.searches, #ygmabt ol.searches { - position:absolute; - z-index:10002; - width:155px; - _width:165px; - padding:7px 7px 3px 7px; - background-color:#ffffff; - border:1px solid #cacaca; - margin:0; -} -#ygma ol.searches li, #ygmabt ol.searches li { - text-align:left; - list-style-type:none; - color:#163780; - margin:0; - padding: 0 0 2px 0; -} -#ygma #ygmapromo ol.searches a, #ygmabt #ygmabtpromo ol.searches a { - font-weight:normal; - color:#163780; -} -#ygma #ygmapromo ol.searches a:hover, #ygmabt #ygmabtpromo ol.searches a:hover { - width:100%; -} -#ygma #ygmapromo-i, #ygmabt #ygmapromo-i { - position:relative; - z-index:10002; - zoom:1; -} -</style><script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['CmJLBkoGYmQ-']='&U=13hve9lct%2fN%3dCmJLBkoGYmQ-%2fC%3d650008.13959238.14033549.12832737%2fD%3dHPRM2%2fB%3d6076890%2fV%3d1'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1avhs34ep%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d2693859403%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=13hve9lct%2fN%3dCmJLBkoGYmQ-%2fC%3d650008.13959238.14033549.12832737%2fD%3dHPRM2%2fB%3d6076890%2fV%3d1"></noscript></div><div id="pa"><div id="pa-wrapper"><ul id="pa2-nav" class="sp"><li class="pa1 sp"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=5/SIG=10np9vmbm;_ylt=A2KIKvTWiH1Ml3sBpAJu.aF4;_ylu=X3oDMTBxbTk2NHViBHBvcwM1BHNlYwNoZWFkZXIEc2xrA3lhaG9v/*http://www.yahoo.com/" class="sp" target="_top">Yahoo!</a></li><li class="pa2 sp"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=6/SIG=1107gluf6;_ylt=A2KIKvTWiH1Ml3sBpQJu.aF4;_ylu=X3oDMTBwMWR0dGFrBHBvcwM2BHNlYwNoZWFkZXIEc2xrA21haWw-/*http://mail.yahoo.com?.intl=us" class="sp" target="_top">Mail</a></li></ul><div id="pa-left" class="sp"></div><ul id="pa-nav" class="sp"><li class="pa3 sp"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=7/SIG=10l2nj3k8;_ylt=A2KIKvTWiH1Ml3sBpgJu.aF4;_ylu=X3oDMTBzMHUyMHRuBHBvcwM3BHNlYwNoZWFkZXIEc2xrA215eWFob28-/*http://my.yahoo.com" class="sp" title="My Yahoo!" target="_top">My Yahoo!</a></li><li class="pa4 sp"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=8/SIG=10niob72s;_ylt=A2KIKvTWiH1Ml3sBpwJu.aF4;_ylu=X3oDMTBwMGtmMDllBHBvcwM4BHNlYwNoZWFkZXIEc2xrA25ld3M-/*http://news.yahoo.com" class="sp" title="Yahoo! News" target="_top">News</a></li><li class="pa5 sp"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=9/SIG=10q40gpus;_ylt=A2KIKvTWiH1Ml3sBqAJu.aF4;_ylu=X3oDMTBzOTQxcmV2BHBvcwM5BHNlYwNoZWFkZXIEc2xrA2ZpbmFuY2U-/*http://finance.yahoo.com" class="sp" title="Yahoo! Finance" target="_top">Finance</a></li><li class="pa6 sp"><a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=10/SIG=10pcalhda;_ylt=A2KIKvTWiH1Ml3sBqQJu.aF4;_ylu=X3oDMTBzN2xhbGU3BHBvcwMxMARzZWMDaGVhZGVyBHNsawNzcG9ydHM-/*http://sports.yahoo.com" class="sp" title="Yahoo! Sports" target="_top">Sports</a></li></ul><div id="pa-right" class="sp"></div></div></div></div><div id="yahoo" class="ygmaclr"><div id="ygmabot"> <a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=11/SIG=10niob72s;_ylt=A2KIKvTWiH1Ml3sBqgJu.aF4;_ylu=X3oDMTB2ajZ1cWFtBHBvcwMxMQRzZWMDaGVhZGVyBHNsawN5YWhvb25ld3M-/*http://news.yahoo.com" id="ygmalogo" target="_top"><img id="ygmalogoimg" width="213" height="26" src="http://l.yimg.com/a/i/brand/purplelogo/uh/us/news.gif" alt="Yahoo! News"></a><div class="ygmacobrand"> Brought to you by <a href="http://us.ard.yahoo.com/SIG=15qa4spbh/M=650008.13019228.13970208.12579242/D=news/S=2023881070:HEAD/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=62FLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5877298/R=12/SIG=10q40gpus;_ylt=A2KIKvTWiH1Ml3sBqwJu.aF4;_ylu=X3oDMTEyZmcwNTJjBHBvcwMxMgRzZWMDaGVhZGVyBHNsawN5YWhvb2ZpbmFuY2U-/*http://finance.yahoo.com" >Yahoo! Finance</a></div></div><div id="ygma-search"><form class="ygmaclr" id="sf" action="http://search.yahoo.com/search" method="GET"><div class="fieldset"> <span class="ygma-search-wrapper" role="search"><label id="ygma-lbl" for="ygmasearchInput" class="offscrn">Search</label> - <input type="text" class="sp normal" id="ygmasearchInput" name="p" value="Search" onblur="if(this.value==''){this.value='Search';this.style.color='#999';this.style.fontWeight='normal';}" onfocus="if(this.value=='Search'){this.value='';this.style.color='#000';this.style.fontWeight='bold';}" maxlength="100" autocomplete="off" /> <input type="hidden" id="fr" name="fr" value="ush-news" /> </span> <span class="ygma-search-wrapper"> <span class="btn sp"> <span class="first-child"> <button name="ygmasrchbtn" id="ygmasrchbtn" value="Web Search" type="submit">Web Search </button> </span> </span> </span></div></form></div></div></div></div></div><script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['62FLBkoGYmQ-']='&U=13gnuteb9%2fN%3d62FLBkoGYmQ-%2fC%3d650008.13019228.13970208.12579242%2fD%3dHEAD%2fB%3d5877298%2fV%3d1'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1av9t9o8q%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d1623204105%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=13gnuteb9%2fN%3d62FLBkoGYmQ-%2fC%3d650008.13019228.13970208.12579242%2fD%3dHEAD%2fB%3d5877298%2fV%3d1"></noscript> </div> -</div> - - - <div id="navigation" role="navigation"> - <ul id="navigation-primary" class="ult-section primary"> - <li class="ult-position"> - <a href="/;_ylt=A2KIKvTWiH1Ml3sBwgJu.aF4;_ylu=X3oDMTEwaDNvdGVmBHBvcwMxBHNlYwN5bl9uYXZpZ2F0aW9uBHNsawNob21l" class="first">Home</a> - </li> - <li class="ult-position"> - <a href="/us;_ylt=A2KIKvTWiH1Ml3sBwwJu.aF4;_ylu=X3oDMTB1cTBsbzdlBHBvcwMyBHNlYwN5bl9uYXZpZ2F0aW9uBHNsawN1cw--" >U.S.</a> - </li> - <li class="ult-position active"> - <a href="/business;_ylt=A2KIKvTWiH1Ml3sBxAJu.aF4;_ylu=X3oDMTE0aHN1Y2w3BHBvcwMzBHNlYwN5bl9uYXZpZ2F0aW9uBHNsawNidXNpbmVzcw--" >Business</a> - </li> - <li class="ult-position"> - <a href="/world;_ylt=A2KIKvTWiH1Ml3sBxQJu.aF4;_ylu=X3oDMTExMGFrbGUxBHBvcwM0BHNlYwN5bl9uYXZpZ2F0aW9uBHNsawN3b3JsZA--" >World</a> - </li> - <li class="ult-position"> - <a href="/entertainment;_ylt=A2KIKvTWiH1Ml3sBxgJu.aF4;_ylu=X3oDMTE4dXQ3bGxrBHBvcwM1BHNlYwN5bl9uYXZpZ2F0aW9uBHNsawNlbnRlcnRhaW5tZW4-" >Entertainment</a> - </li> - <li class="ult-position"> - <a href="/sports;_ylt=A2KIKvTWiH1Ml3sBxwJu.aF4;_ylu=X3oDMTEyNGE3Ym4yBHBvcwM2BHNlYwN5bl9uYXZpZ2F0aW9uBHNsawNzcG9ydHM-" >Sports</a> - </li> - <li class="ult-position"> - <a href="/technology;_ylt=A2KIKvTWiH1Ml3sByAJu.aF4;_ylu=X3oDMTEwMzU2dXNxBHBvcwM3BHNlYwN5bl9uYXZpZ2F0aW9uBHNsawN0ZWNo" >Tech</a> - </li> - <li class="ult-position"> - <a href="/politics;_ylt=A2KIKvTWiH1Ml3sByQJu.aF4;_ylu=X3oDMTE0aXM0ZGJiBHBvcwM4BHNlYwN5bl9uYXZpZ2F0aW9uBHNsawNwb2xpdGljcw--" >Politics</a> - </li> - <li class="ult-position"> - <a href="/science;_ylt=A2KIKvTWiH1Ml3sBygJu.aF4;_ylu=X3oDMTEzb2N0YW5yBHBvcwM5BHNlYwN5bl9uYXZpZ2F0aW9uBHNsawNzY2llbmNl" >Science</a> - </li> - <li class="ult-position"> - <a href="/health;_ylt=A2KIKvTWiH1Ml3sBywJu.aF4;_ylu=X3oDMTEzZjg3dnFmBHBvcwMxMARzZWMDeW5fbmF2aWdhdGlvbgRzbGsDaGVhbHRo" >Health</a> - </li> - <li class="ult-position"> - <a href="/opinion;_ylt=A2KIKvTWiH1Ml3sBzAJu.aF4;_ylu=X3oDMTE0dmhlYmJoBHBvcwMxMQRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDb3Bpbmlvbg--" >Opinion</a> - </li> - <li class="ult-position"> - <a href="/most-popular;_ylt=A2KIKvTWiH1Ml3sBzQJu.aF4;_ylu=X3oDMTE4MWJkMmc0BHBvcwMxMgRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDbW9zdHBvcHVsYXI-" >Most Popular</a> - </li> - </ul><!-- end: .primary --> - - <ul id="navigation-secondary" class="ult-section secondary"> - <li class="ult-position"> - <a href="/video/business;_ylt=A2KIKvTWiH1Ml3sBzgJu.aF4;_ylu=X3oDMTE5NnR1ZmJtBHBvcwMxMwRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDYnVzaW5lc3N2aWRl" class="first">Business Video</a> - </li> - <li class="ult-position"> - <a href="/business/us-economy;_ylt=A2KIKvTWiH1Ml3sBzwJu.aF4;_ylu=X3oDMTE2ZWRqZGV1BHBvcwMxNARzZWMDeW5fbmF2aWdhdGlvbgRzbGsDdXNlY29ub215" >U.S. Economy</a> - </li> - <li class="ult-position"> - <a href="/business/stock-markets;_ylt=A2KIKvTWiH1Ml3sB0AJu.aF4;_ylu=X3oDMTE5djRtNDlsBHBvcwMxNQRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDc3RvY2ttYXJrZXRz" >Stock Markets</a> - </li> - <li class="ult-position"> - <a href="/business/earnings;_ylt=A2KIKvTWiH1Ml3sB0QJu.aF4;_ylu=X3oDMTE1NGo5YWNuBHBvcwMxNgRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDZWFybmluZ3M-" >Earnings</a> - </li> - <li class="ult-position"> - <a href="/business/opinion;_ylt=A2KIKvTWiH1Ml3sB0gJu.aF4;_ylu=X3oDMTE0NTVoZXBsBHBvcwMxNwRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDb3Bpbmlvbg--" >Opinion</a> - </li> - <li class="ult-position"> - <a href="/business/personal-finance;_ylt=A2KIKvTWiH1Ml3sB0wJu.aF4;_ylu=X3oDMTE5a2xyZjMyBHBvcwMxOARzZWMDeW5fbmF2aWdhdGlvbgRzbGsDcGVyc29uYWxmaW5h" >Personal Finance</a> - </li> - <li class="ult-position"> - <a href="/business/press-releases;_ylt=A2KIKvTWiH1Ml3sB1AJu.aF4;_ylu=X3oDMTE5MDBuYTNrBHBvcwMxOQRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDcHJlc3NyZWxlYXNl" >Press Releases</a> - </li> - <li class="ult-position"> - <a href="/business/taxes;_ylt=A2KIKvTWiH1Ml3sB1QJu.aF4;_ylu=X3oDMTEybnZnZjA1BHBvcwMyMARzZWMDeW5fbmF2aWdhdGlvbgRzbGsDdGF4ZXM-" >Taxes</a> - </li> - <li class="ult-position"> - <a href="http://us.lrd.yahoo.com/SIG=11706rlnl;_ylt=A2KIKvTWiH1Ml3sB1gJu.aF4;_ylu=X3oDMTE4ZzZ1cjZ0BHBvcwMyMQRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDbWFya2V0cGxhY2U-/**http%3A//marketplace.news.yahoo.net/" >Marketplace</a> - </li> - <li class="ult-position"> - <a href="/io/3729;_ylt=A2KIKvTWiH1Ml3sB1wJu.aF4;_ylu=X3oDMTE3dDA4cnVnBHBvcwMyMgRzZWMDeW5fbmF2aWdhdGlvbgRzbGsDbmV3c21ha2Vycw--" >Newsmakers</a> - </li> - </ul><!-- end: .secondary --> - </div><!-- end: #navigation --> - - - <div id="yn-popular-searches" class="mod"> - <form action="http://news.search.yahoo.com/news/search" method="get" role="search"> - <input type="hidden" name="ei" value="UTF-8"/> - <input type="hidden" name="fr" value="news-us-ss"/> - <ul> - <li class="search-type yn-menu"> - <a href="#" class="menu-trigger">search menu</a> - <div class="menu-bd"> - <fieldset class="menu-content"> - <legend>Search Type</legend> - - <span>Choose a search type from the items below</span> - - <input type="radio" name="c" value="" id="search_all" checked="checked"> - <label for="search_all" class="first">All News</label> - - <input type="radio" name="c" value="yahoo_news" id="search_ynews"> - <label for="search_ynews">Yahoo! News Only</label> - - <input type="radio" name="c" value="images" id="search_images"> - <label for="search_images">News Photos</label> - - <input type="radio" name="c" value="av" id="search_av"> - <label for="search_av">Video/Audio</label> - </fieldset> - </div> - </li> - <li class="search-text"><input id="yn-search-assist" type="text" name="p" value=""></li> - <li class="search-submit"><button type="submit">News Search</button></li> - </ul> - </form> - <h3>Trending Now:</h3> - <div class="popular-searches"> - <ul> - <li class="first"> - <a href="http://us.lrd.yahoo.com/SIG=12m995i7j;_ylt=A2KIKvTWiH1Ml3sB7AJu.aF4;_ylu=X3oDMTFhcWUzb2VxBHBvcwMxBHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNsYWJhcmJpZQ--/**http%3A//news.search.yahoo.com/news/search%3Fp=la%2Bbarbie%26fr=news-us-tts%26cs=bz" tabindex="-1">la barbie</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=138271j08;_ylt=A2KIKvTWiH1Ml3sB7QJu.aF4;_ylu=X3oDMTFlZHJ2cWY2BHBvcwMyBHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNuYXRpb25hbGh1cnI-/**http%3A//news.search.yahoo.com/news/search%3Fp=national%2Bhurricane%2Bcenter%26fr=news-us-tts%26cs=bz" tabindex="-1">national hurricane center</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12ro02656;_ylt=A2KIKvTWiH1Ml3sB7gJu.aF4;_ylu=X3oDMTFlaXJ0anJjBHBvcwMzBHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNodXJyaWNhbmVlYXI-/**http%3A//news.search.yahoo.com/news/search%3Fp=hurricane%2Bearl%26fr=news-us-tts%26cs=bz" tabindex="-1">hurricane earl</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=137lfq354;_ylt=A2KIKvTWiH1Ml3sB7wJu.aF4;_ylu=X3oDMTFlb2RrdWhlBHBvcwM0BHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNkYW5jaW5nd2l0aHQ-/**http%3A//news.search.yahoo.com/news/search%3Fp=dancing%2Bwith%2Bthe%2Bstars%26fr=news-us-tts%26cs=bz" tabindex="-1">dancing with the stars</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12o8cksd5;_ylt=A2KIKvTWiH1Ml3sB8AJu.aF4;_ylu=X3oDMTFjdTE3OTN0BHBvcwM1BHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNqb2huY3VzYWNr/**http%3A//news.search.yahoo.com/news/search%3Fp=john%2Bcusack%26fr=news-us-tts%26cs=bz" tabindex="-1">john cusack</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12nh7d92u;_ylt=A2KIKvTWiH1Ml3sB8QJu.aF4;_ylu=X3oDMTFiazA4Y25hBHBvcwM2BHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNnbGVubmJlY2s-/**http%3A//news.search.yahoo.com/news/search%3Fp=glenn%2Bbeck%26fr=news-us-tts%26cs=bz" tabindex="-1">glenn beck</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12qhuj6ln;_ylt=A2KIKvTWiH1Ml3sB8gJu.aF4;_ylu=X3oDMTFlbnI1bTczBHBvcwM3BHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawN0cm95cG9sYW1hbHU-/**http%3A//news.search.yahoo.com/news/search%3Fp=troy%2Bpolamalu%26fr=news-us-tts%26cs=bz" tabindex="-1">troy polamalu</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12quq8bhi;_ylt=A2KIKvTWiH1Ml3sB8wJu.aF4;_ylu=X3oDMTFlM2FvZTFoBHBvcwM4BHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNsaW5kc2F5bG9oYW4-/**http%3A//news.search.yahoo.com/news/search%3Fp=lindsay%2Blohan%26fr=news-us-tts%26cs=bz" tabindex="-1">lindsay lohan</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12fakqcsr;_ylt=A2KIKvTWiH1Ml3sB9AJu.aF4;_ylu=X3oDMTE2Z2xpbnIxBHBvcwM5BHNlYwN5bl9wb3B1bGFyX3NlYXJjaGVzBHNsawNpcmFu/**http%3A//news.search.yahoo.com/news/search%3Fp=iran%26fr=news-us-tts%26cs=bz" tabindex="-1">iran</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=133b05m4d;_ylt=A2KIKvTWiH1Ml3sB9QJu.aF4;_ylu=X3oDMTFmNmNobjZvBHBvcwMxMARzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDdHJvcGljYWxzdG9y/**http%3A//news.search.yahoo.com/news/search%3Fp=tropical%2Bstorm%2Bfiona%26fr=news-us-tts%26cs=bz" tabindex="-1">tropical storm fiona</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=137ea347r;_ylt=A2KIKvTWiH1Ml3sB9gJu.aF4;_ylu=X3oDMTFmMjQ1aGx1BHBvcwMxMQRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDbmF0aW9uYWx3ZWF0/**http%3A//news.search.yahoo.com/news/search%3Fp=national%2Bweather%2Bservice%26fr=news-us-tts%26cs=bz" tabindex="-1">national weather service</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12r60of62;_ylt=A2KIKvTWiH1Ml3sB9wJu.aF4;_ylu=X3oDMTFmNjhkZGZuBHBvcwMxMgRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDc2FuZHJhYnVsbG9j/**http%3A//news.search.yahoo.com/news/search%3Fp=sandra%2Bbullock%26fr=news-us-tts%26cs=bz" tabindex="-1">sandra bullock</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12j69jn9p;_ylt=A2KIKvTWiH1Ml3sB.AJu.aF4;_ylu=X3oDMTFiZzhpbzJhBHBvcwMxMwRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDZGlhYmV0ZXM-/**http%3A//news.search.yahoo.com/news/search%3Fp=diabetes%26fr=news-us-tts%26cs=bz" tabindex="-1">diabetes</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12s7e2era;_ylt=A2KIKvTWiH1Ml3sB.QJu.aF4;_ylu=X3oDMTFmYWxpODViBHBvcwMxNARzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDbWljaGFlbGphY2tz/**http%3A//news.search.yahoo.com/news/search%3Fp=michael%2Bjackson%26fr=news-us-tts%26cs=bz" tabindex="-1">michael jackson</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=13266q95c;_ylt=A2KIKvTWiH1Ml3sB.gJu.aF4;_ylu=X3oDMTFmZmZxczB2BHBvcwMxNQRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDdGhld2VhdGhlcmNo/**http%3A//news.search.yahoo.com/news/search%3Fp=the%2Bweather%2Bchannel%26fr=news-us-tts%26cs=bz" tabindex="-1">the weather channel</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12oq5hogt;_ylt=A2KIKvTWiH1Ml3sB.wJu.aF4;_ylu=X3oDMTFkZHZqYm9pBHBvcwMxNgRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDZW1teWF3YXJkcw--/**http%3A//news.search.yahoo.com/news/search%3Fp=emmy%2Bawards%26fr=news-us-tts%26cs=bz" tabindex="-1">emmy awards</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12gcjs4uq;_ylt=A2KIKvTWiH1Ml3sB_AJu.aF4;_ylu=X3oDMTE4ZjAzbzh0BHBvcwMxNwRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDY2hpbmE-/**http%3A//news.search.yahoo.com/news/search%3Fp=china%26fr=news-us-tts%26cs=bz" tabindex="-1">china</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12sb3bbpb;_ylt=A2KIKvTWiH1Ml3sB_QJu.aF4;_ylu=X3oDMTFmcjJsamFnBHBvcwMxOARzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDa3Jpc3RlbnN0ZXdh/**http%3A//news.search.yahoo.com/news/search%3Fp=kristen%2Bstewart%26fr=news-us-tts%26cs=bz" tabindex="-1">kristen stewart</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12tt768tl;_ylt=A2KIKvTWiH1Ml3sB_gJu.aF4;_ylu=X3oDMTFmaDdsazhnBHBvcwMxOQRzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDcm9iZXJ0cGF0dGlu/**http%3A//news.search.yahoo.com/news/search%3Fp=robert%2Bpattinson%26fr=news-us-tts%26cs=bz" tabindex="-1">robert pattinson</a> - </li> - <li> - <a href="http://us.lrd.yahoo.com/SIG=12v63l9vo;_ylt=A2KIKvTWiH1Ml3sB_wJu.aF4;_ylu=X3oDMTFmdTZyaDZiBHBvcwMyMARzZWMDeW5fcG9wdWxhcl9zZWFyY2hlcwRzbGsDZ2xlbm5iZWNrcmFs/**http%3A//news.search.yahoo.com/news/search%3Fp=glenn%2Bbeck%2Brally%26fr=news-us-tts%26cs=bz" tabindex="-1">glenn beck rally</a> - </li> - </ul> - </div> -</div> - - - <!-- SpaceID=2023881070 loc=NT1 noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['_mFLBkoGYmQ-']='&U=12bltukco%2fN%3d_mFLBkoGYmQ-%2fC%3d-1%2fD%3dNT1%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1ave911lr%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d2814510456%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12bltukco%2fN%3d_mFLBkoGYmQ-%2fC%3d-1%2fD%3dNT1%2fB%3d-1%2fV%3d0"></noscript> - - - - </div> - - <div id="bd"> - - <div id="yui-main"> - - <div class="yui-b" role="main" aria-labelledby="yn-title"> - - -<div class="promobar mod promobar-top_bar"> - - <div class="normal blue"> - <div class="bd"> - <a href="http://news.yahoo.com/s/ynews/ynews_ts3464;_ylt=A2KIKvTWiH1Ml3sBAANu.aF4;_ylu=X3oDMTE1cDJvbGFlBHBvcwMxBHNlYwN5bl9wcm9tb3NfdG9wX2JhcgRzbGsDaW1hZ2U-" class="media"><img src="http://l.yimg.com/a/p/us/news/editorial/2/ed/2ed2bc5cd613c950b9d43c6da65ab7b2.jpeg" width="120" height="39" alt=""></a> - <h4><a href="http://news.yahoo.com/s/ynews/ynews_ts3464;_ylt=A2KIKvTWiH1Ml3sBAQNu.aF4;_ylu=X3oDMTFjbDZhZTBwBHBvcwMyBHNlYwN5bl9wcm9tb3NfdG9wX2JhcgRzbGsDNXllYXJzYWZ0ZXJr" ><strong>5 YEARS AFTER KATRINA:</strong>Special report includes slideshows and multimedia</a></h4> - <a href="http://news.yahoo.com/s/ynews/ynews_ts3464;_ylt=A2KIKvTWiH1Ml3sBAgNu.aF4;_ylu=X3oDMTEwM2htOGVvBHBvcwMzBHNlYwN5bl9wcm9tb3NfdG9wX2JhcgRzbGsD" ></a> - </div><!-- end bd --> - </div> - -</div> - -<div id="yn-story" class="ult-section mod normal-entry"> - - <div class="hd"> - - - - <h1 id="yn-title">GM expects competitive Chevy Volt pricing in China</h1> - <a href="http://us.rd.yahoo.com/dailynews/reuters/brand/SIG=pd7i95;_ylt=A2KIKvTWiH1Ml3sBAwNu.aF4;_ylu=X3oDMTExaTJpa3NyBHBvcwMxBHNlYwN5bi1wcnZkbGluawRzbGsDcmV1dGVycw--/*http://www.reuters.com" id="yn-prvdlink" class="provider-logo ult-section"> - <img src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" width="106" height="27" alt="Reuters" class="lzbg" style="background-image: url(http://l.yimg.com/a/p/us/news/editorial/d/0c/d0c3eb8ca18907492a4b337b5cec5193.jpeg);"> </a> - - - - <div id="yup-container"></div> - <script type="text/javascript"> - - if(!YAHOO){var YAHOO = {};} - - YAHOO.BuzzWidgetTries = 0; - - (function(){ - if(YAHOO && YAHOO.util && YAHOO.util.Event && YAHOO.Media && YAHOO.Media.Buzz){ - (function(){ var buzz = new YAHOO.Media.Buzz("buzz-top",{"sync":"buzz-bottom","countPosition":"after","fetchCount":false,"loc_strings":{"buzz_up":"Buzz up!","buzzed":"Buzzed!","one_vote":"{0} vote","n_votes":"{0} votes"}});buzz.onSuccess.subscribe(function(){ if(YAHOO.Updates){ YAHOO.Updates.Disclosure.showDialog({"container":"yup-container","source":"buzz","type":"buzzUp","lang":"en-US"}); } }); })();(function(){ var buzz = new YAHOO.Media.Buzz("buzz-bottom",{"sync":"buzz-top","countPosition":"after","fetchCount":true,"loc_strings":{"buzz_up":"Buzz up!","buzzed":"Buzzed!","one_vote":"{0} vote","n_votes":"{0} votes"}});buzz.onSuccess.subscribe(function(){ if(YAHOO.Updates){ YAHOO.Updates.Disclosure.showDialog({"container":"yup-container","source":"buzz","type":"buzzUp","lang":"en-US"}); } }); })(); - } else if(YAHOO.BuzzWidgetTries < 10000) { - YAHOO.BuzzWidgetTries += 500; - setTimeout(arguments.callee, 500); - } - })(); - - </script> - - - <script type='text/javascript'> - if (typeof YAHOO == "undefined") { YAHOO = {}; } - if (typeof YAHOO.Media == "undefined") { YAHOO.Media = {}; } - if (typeof YAHOO.Media.SocialButtons == "undefined") { YAHOO.Media.SocialButtons = {}; } - var o_facebook_iframe_url="http://l.yimg.com/b/social_buttons/facebook-share-iframe.php?u={url}&t={title}"; - YAHOO.Media.SocialButtons.conf = { - content: { - url: "http:\/\/news.yahoo.com\/s\/nm\/20100831\/bs_nm\/us_gm_china", - title: "GM+expects+competitive+Chevy+Volt+pricing+in+China+-+Yahoo%21+News", - mail_locale: "us", - mail_property: "news", - mail_meta: "&h1=nm/20100831/bs_nm/us_gm_china&h2=T&h3=568", - print_url_template: "{url}/print" - }, - config: { facebook_iframe_url: o_facebook_iframe_url } - } - </script> - <ul id="top" class="tools mod ult-section"> - <li class="buzz ult-position"> - - - <form method="post" action="http://buzz.yahoo.com/vote/" class="buzz" id="buzz-top"> - <input type="hidden" name="publisherurn" value="y_news"> - <input type="hidden" name="guid" value="nm/20100831/us_gm_china"> - <input type="hidden" name=".done" value="/article/y_news/nm/20100831/us_gm_china"> - <input type="hidden" name="assettype" value="article"> - <input type="hidden" name="votetype" value="1"> - <input type="hidden" name="from" value="orion"> - <input type="hidden" name="redirect" value="1"> - <input type="hidden" name="key" value="b1713"> - <input type="hidden" name=".crumb" value="OqSudfuYUf0"> - <input type="hidden" name="logged" value="0"> - <input type="hidden" name="language" value="en-US"> - <input type="hidden" name="market" value="us"> - <button type="submit">Buzz up!<span class="right"></span></button> - </form> - - </li> - <li> - <div class="ymsb ymsb-facebook ymsb-retweet ymsb-mail ymsb-print"></div> - </li> - </ul> - <!-- end: .tools --> - - - - - - - </div><!-- end: .hd --> - - <div class="bd"> - - <div id="yn-story-related-media"> - - <div class="primary-media"> - - <div id="yn-story-main-media" class="ult-section yn-style1"> - <div class="photo-big"> - <a href="/nphotos/Dan-Akerson-poses-portrait-General-Motors-headquarters-Detroit-Michigan-August/photo//100831/ids_photos_wl/r3693855494.jpg//s:/nm/20100831/bs_nm/us_gm_china;_ylt=A2KIKvTWiH1Ml3sBBANu.aF4;_ylu=X3oDMTE5OWNjZzkzBHBvcwMxBHNlYwN5bl9yX3RvcF9waG90bwRzbGsDZGFuYWtlcnNvbnBv" class="media "> - <img src="http://d.yimg.com/a/p/rids/20100831/i/r3693855494.jpg?x=213&y=266&xc=1&yc=1&wc=360&hc=450&q=85&sig=RlxfV1lru0l6HybRpN1wbg--" width="213" height="266" alt="Dan Akerson poses for a portrait at General Motors headquarters in Detroit"> - - </a> - - <cite class="caption"> - Reuters&nbsp;&ndash;&nbsp;Dan Akerson poses for a portrait at General Motors headquarters in Detroit, Michigan, August 31, 2010.&nbsp;&hellip; </cite> - </div> - - -</div><!-- end #main-media --> - - - <div id="yn-story-minor-media"> - - <ul id="yn-story-related-links" class="list2 list6 size1 ult-section yn-style3"> - <li class="ult-position first slideshow"> - <a href="/nphotos/General-Motors-Corp/ss/events/bs/031605gmgeneralmotor;_ylt=A2KIKvTWiH1Ml3sBBQNu.aF4;_ylu=X3oDMTFmNWF1ZmcwBHBvcwMyBHNlYwN5bl9yXzNzbG90X3NsaWRlc2hvdwRzbGsDc2xpLWV2LXRodW1i" class="media media1"> - <img src="http://d.yimg.com/a/p/rids/20100831/t/r3693855494.jpg?x=50&y=50&xc=1&yc=1&wc=103&hc=103&q=85&sig=IH1VnATi.fuhp1hdxoC1ow--" width="50" height="50" alt="General Motors Corp."> - - </a> - - <a href="/nphotos/General-Motors-Corp/ss/events/bs/031605gmgeneralmotor;_ylt=A2KIKvTWiH1Ml3sBBgNu.aF4;_ylu=X3oDMTFlcWFuNHZ0BHBvcwMzBHNlYwN5bl9yXzNzbG90X3NsaWRlc2hvdwRzbGsDc2xpLWV2LWxpbms-" ><strong>Slideshow:</strong>General Motors Corp.</a> - </li> - </ul> - - <div id="yn-story-quotes" class="ult-section"> - <table cellspacing="0"> - <caption>Related Quotes</caption> - <thead> - <tr> - <th>Symbol</th> - <th>Price</th> - <th>Change</th> - </tr> - </thead> - <tbody> - <tr class="ult-position alternative"> - <td class="first"> - <a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBBwNu.aF4;_ylu=X3oDMTEyZ3M0M3FjBHBvcwM0BHNlYwN5bl9yXzNzbG90X3N0b2NrBHNsawNkamk-?s=^DJI" > - <abbr title="Dow Jones Industrial Average">^DJI</abbr> - </a> - </td> - <td>10,014.72</td> - <td class="positive">+4.99</td> - </tr> - <tr class="ult-position "> - <td class="first"> - <a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBCANu.aF4;_ylu=X3oDMTEzcnI2cXJvBHBvcwM1BHNlYwN5bl9yXzNzbG90X3N0b2NrBHNsawNnc3Bj?s=^GSPC" > - <abbr title="S&P 500 INDEX,RTH">^GSPC</abbr> - </a> - </td> - <td>1,049.33</td> - <td class="positive">+0.41</td> - </tr> - <tr class="ult-position alternative"> - <td class="first"> - <a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBCQNu.aF4;_ylu=X3oDMTEzZGx0ZzFqBHBvcwM2BHNlYwN5bl9yXzNzbG90X3N0b2NrBHNsawNpeGlj?s=^IXIC" > - <abbr title="NASDAQ Composite">^IXIC</abbr> - </a> - </td> - <td>2,114.03</td> - <td class="negative">-5.94</td> - </tr> - </tbody> - </table> - - <!-- SpaceID=2023881070 loc=FB noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['52FLBkoGYmQ-']='&U=12a4sivt8%2fN%3d52FLBkoGYmQ-%2fC%3d-1%2fD%3dFB%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1avo894p4%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d4189814197%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12a4sivt8%2fN%3d52FLBkoGYmQ-%2fC%3d-1%2fD%3dFB%2fB%3d-1%2fV%3d0"></noscript><!-- SpaceID=2023881070 loc=FB noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['6GFLBkoGYmQ-']='&U=12ao3j7qk%2fN%3d6GFLBkoGYmQ-%2fC%3d-1%2fD%3dFB%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1auestpa3%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d345958507%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12ao3j7qk%2fN%3d6GFLBkoGYmQ-%2fC%3d-1%2fD%3dFB%2fB%3d-1%2fV%3d0"></noscript></div> - - </div> - - </div><!-- end .primary-media --> - - - </div><!-- end .related-media --> - - - - - -<div class="byline"> - <cite class="vcard"> - - - <span class="fn org"> - - </span> - </cite> - &ndash; - <abbr title="2010-08-31T04:21:56-0700" class="timedate">Tue&nbsp;Aug&nbsp;31, 7:21&nbsp;am&nbsp;ET</abbr></div><!-- end .byline --> - - - - <div class="yn-story-content"> - <p>SHANGHAI (Reuters) &ndash; General Motors expects competitive pricing for its electric Chevrolet Volt in China as it hopes to gain a foothold in China&#39;s fledgling environmentally friendly car industry with the highly anticipated car.</p> - <p> -Kevin Wale, president and managing director of GM China, said the launch of Chevrolet Volt, expected in the second half of 2011, is key to the Detroit-based carmaker&#39;s broader strategy to grow its electric car business in China after Beijing unveiled subsidy measures for the sector.</p> - <p> -Wale said the selling price of Chevrolet Volt in China will only be unveiled when it is officially launched.</p> - <p> -&quot;I believe the pricing will be competitive,&quot; he told a news conference in Shanghai on Tuesday.</p> - <p> -In July, GM said its electric Chevrolet Volt will be sold in the United States at &#36;41,000 -- &#36;8,000 more than its nearest competitor, the Nissan Leaf.</p> - <p> -The U.S. automaker expects to produce 10,000 Volts for the 2011 model year and about 30,000 for 2012.</p> - <p> -China will spend more than 100 billion yuan (&#36;14.70 billion) to subsidize the electric car industry over the next 10 years, local media reported.</p> - <p> -&quot;China is the country that needs to move to electrification more than any other country and we know the government wants to move to electrification so we want to participate in that movement,&quot; Wale said.</p> - <p> -However, the Chevrolet Volt will likely face fierce competition in the domestic market where smaller rivals are making and selling similar products at a much lower price.</p> - <p> -Chinese carmaker BYD, backed by U.S. billionaire Warren Buffett, said it plans to export large quantities of its E6 electric car in the United States where it will be competing head on with the likes of Nissan&#39;s Leaf and GM&#39;s Volt.</p> - <p> -Despite rising competition, Wale said GM China has not sacrificed its profitability for sales growth.</p> - <p> -&quot;It&#39;s our responsibility to be successful in China, successful means to grow volume in China, and successful means to generate profitability for our shareholders and to generate enough cash to be reinvested in our business,&quot; he said.</p> - <p> -&quot;We are doing all of those and we are going to continue to run a strong, balanced business in China and we will continue to grow,&quot; he said.</p> - <p> -(&#36;1=6.802 Yuan)</p> - <p> -(Reporting by Soo Ai Peng; Editing by Jacqueline Wong)</p> - <p></p> - </div> -<div class="yn-share-social">Follow Yahoo! News on <a class="twitter" href="http://twitter.com/yahoonews">Twitter</a>, become a fan on <a class="facebook" href="http://www.facebook.com/yahoonews">Facebook</a></div> - - </div><!-- end: .bd --> - - <div class="ft"> - - - - <div id="yn-story-share"> - - - <ul id="bottom" class="tools mod ult-section"> - <li class="buzz ult-position"> - - - <form method="post" action="http://buzz.yahoo.com/vote/" class="buzz" id="buzz-bottom"> - <input type="hidden" name="publisherurn" value="y_news"> - <input type="hidden" name="guid" value="nm/20100831/us_gm_china"> - <input type="hidden" name=".done" value="/article/y_news/nm/20100831/us_gm_china"> - <input type="hidden" name="assettype" value="article"> - <input type="hidden" name="votetype" value="1"> - <input type="hidden" name="from" value="orion"> - <input type="hidden" name="redirect" value="1"> - <input type="hidden" name="key" value="b1713"> - <input type="hidden" name=".crumb" value="OqSudfuYUf0"> - <input type="hidden" name="logged" value="0"> - <input type="hidden" name="language" value="en-US"> - <input type="hidden" name="market" value="us"> - <button type="submit">Buzz up!<span class="right"></span></button> - </form> - - </li> - <li> - <div class="ymsb ymsb-facebook ymsb-retweet ymsb-mail ymsb-print"></div> - </li> - </ul> - <!-- end: .tools --> - - - - </div> - - <script type="text/javascript"> -(function() { - - /** - * YUI function to get all elements by class name - * getElementsByClass - */ - function getElementsByClass(className, tag, root) { - tag = tag || '*'; - root = (typeof root == 'string') ? document.getElementById(root) : root || document; - - var nodes = [], - elements = root.getElementsByTagName(tag), - re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)'); - - for (var i = 0, len = elements.length; i < len; ++i) { - if ( re.test(elements[i].className) ) { - nodes[nodes.length] = elements[i]; - } - } - return nodes; - } - - /** - * This code displays the expand button as the page loads. This prevents any lag in displaying - * the button if the page takes a long time to load. If the user clicks the button before the page - * loads then we just show the full story without animation. Thus, preventing the full implementation - * from loading. - */ - var story = document.getElementById('yn-story'); - var related = document.getElementById('yn-story-related-media'); - var expandCookie = '0'; - - if ( - // make sure read-more-toggle flag exists - (story && story.className.indexOf('read-more-toggle') != -1) && - // if user preference is 0 then collapse story - expandCookie == '0' && - // make sure hash is not set - window.location.hash!='#full' - ) - { - var buttonHeight = 18; - var defaultHeight = 406; - var heightLimit = 200; - var host = document.location.host; - var path = document.location.pathname; - var bd = getElementsByClass('bd','div',story)[0]; - - // Figure out min height for story container based on the heights of the related media and primary media containers - if (related) - { - // do height calculations if related height is larger than default minHeight - if (related.offsetHeight > defaultHeight) { - - // if primary exists then use that height, otherwise use related - var obj = getElementsByClass('primary-media','div',related)[0]; - if (!obj) obj = related; - - // set height from primary or related container - minHeight = parseInt(obj.offsetHeight, 10); - - // if the minheight is smaller than default, then set it to default - if ( - (minHeight < defaultHeight) && - !document.getElementById('yn-story-main-media') && - !document.getElementById('yn-story-minor-media') - ) - { - minHeight = defaultHeight; - } - } else { - minHeight = defaultHeight; - } - } else { - minHeight = defaultHeight; - } - - // add height of buttons to min height - minHeight += buttonHeight; - - // make sure there is enough story to hide - if ((bd.offsetHeight - minHeight) > heightLimit) - { - // set overflow and height - bd.style.height = minHeight + "px"; - bd.className += " overflow"; - story.className += " read-closed"; - - // add read more expand button - var div = document.createElement('div'); - div.className = 'read-more read-more-expand'; - div.innerHTML = '<a href="http://'+host+path+'" id="yn-bodycontrol" class="ult-nofollow ult-section" accesskey="m" title="Press CTRL + SHIFT + M to toggle" mapleUltPriority="1000"><em><span>Read Full Article</span></em></a>'; - - // we have to handle the case where a user might click the button before our YUI script loads - // if so, remove the height and overflow setting then hide the button, also add #full hash to url so - // if script eventually is loaded it doesnt recreate the buttons and collapse the story - div.onclick = function(ev) { - var e = ev || window.event; - - // show full story - bd.style.height = 'auto'; - bd.className = bd.className.replace('overflow','expanded'); - story.className = story.className.replace('read-closed',''); - - // hide the toggle button - div.className += ' hide'; - - // disable link follow - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } - } - - // add div to document - bd.appendChild(div); - } - - } - -})(); -</script> - - </div><!-- end .ft --> - -</div><!-- end: #story --> - -<div id="yn-story-related-content" class="yui-g mod related-content-style"> - - <div id="yn-r-b-left" class="yui-u first ult-section"> - - <h3>More on Asia</h3> - - <ul class="list list4 list6 size1"> - <li class="first"> - <a href="/s/nm/20100831/bs_nm/us_usa_china_currency_4;_ylt=A2KIKvTWiH1Ml3sBCgNu.aF4;_ylu=X3oDMTE2OTFkNXQxBHBvcwMxBHNlYwN5bi1yLWItbGVmdARzbGsDZXYtdS5zLnR1cm5z" class="showtt" rel=":nm:20100831:bs_nm:us_usa_china_currency_4"> - U.S. turns down China currency probes in two cases </a> - - <cite>Reuters</cite> - </li> - <li class=""> - <a href="/s/ap/20100831/ap_on_bi_ge/us_fed_china_morgan_stanley_2;_ylt=A2KIKvTWiH1Ml3sBCwNu.aF4;_ylu=X3oDMTE2MmM4ZGVuBHBvcwMyBHNlYwN5bi1yLWItbGVmdARzbGsDZXYtZmVkYXBwcm92" class="showtt" rel=":ap:20100831:ap_on_bi_ge:us_fed_china_morgan_stanley_2"> - Fed approves sale of Morgan Stanley stock shares </a> - - <cite>AP</cite> - </li> - <li class=""> - <a href="/s/nm/20100831/wl_nm/us_afghanistan_3" class="showtt" rel=":nm:20100831:wl_nm:us_afghanistan_3"> - Afghan withdrawal won&#39;t be a &quot;hand-off&quot;: Petraeus </a> - - <cite>Reuters</cite> - </li> - </ul> - - <a href="http://news.yahoo.com/i/2227;_ylt=A2KIKvTWiH1Ml3sBDANu.aF4;_ylu=X3oDMTE4azhjOTIwBHBvcwM0BHNlYwN5bl9zdG9yeV9yZWxhdGVkBHNsawNtb3JlcmFxdW8-" class="more size1">More &raquo;</a> - - </div> - - <div id="yn-r-b-right" class="yui-u ult-section"> - - <h3>More...</h3> - - <ul class="list list4 list6 size1"> - <li class="first video"> - <a href="http://us.rd.yahoo.com/dailynews/external/cnbc/av_cnbc/93f653138348df2d6551824f0a0b2bb3/37408778;_ylt=A2KIKvTWiH1Ml3sBDQNu.aF4;_ylu=X3oDMTE2NGFhbTlnBHBvcwM1BHNlYwN5bi1yLWItcmlnaHQEc2xrA3ZpZC11bi1saW5r/*http://news.yahoo.com/video/business-15749628/21680520" class="showtt" rel=":cnbc:20100831:av_cnbc:_yahoonewsvideo_business1579288183__21680516"> - <strong>Business Video:</strong> - Should You Buy Housing Stocks on the Dip? </a> - - <cite> - <a href="/i/3245;_ylt=A2KIKvTWiH1Ml3sBDgNu.aF4;_ylu=X3oDMTE3amNpYnZ0BHBvcwM2BHNlYwN5bi1yLWItcmlnaHQEc2xrA3ZpZC11bi1wcm92aQ--" >CNBC</a> - </cite> - </li> - <li class="video"> - <a href="http://us.rd.yahoo.com/dailynews/external/cnbc/av_cnbc/3a6c3e44ad7166a0edd3b8437b1b1fc5/37408779;_ylt=A2KIKvTWiH1Ml3sBDwNu.aF4;_ylu=X3oDMTE2YWxsMDF1BHBvcwM3BHNlYwN5bi1yLWItcmlnaHQEc2xrA3ZpZC11bi1saW5r/*http://news.yahoo.com/video/business-15749628/21680509" class="showtt" rel=":cnbc:20100831:av_cnbc:_yahoonewsvideo_business1579339410__21680505"> - <strong>Business Video:</strong> - Who&#39;s &quot;Too Big To Fail?&quot; </a> - - <cite> - <a href="/i/3245;_ylt=A2KIKvTWiH1Ml3sBEANu.aF4;_ylu=X3oDMTE3cWFiMGJmBHBvcwM4BHNlYwN5bi1yLWItcmlnaHQEc2xrA3ZpZC11bi1wcm92aQ--" >CNBC</a> - </cite> - </li> - <li class="video"> - <a href="http://us.rd.yahoo.com/dailynews/external/cnbc/av_cnbc/63754bac73cb68616defc8ff42818aa3/37408780;_ylt=A2KIKvTWiH1Ml3sBEQNu.aF4;_ylu=X3oDMTE2Y25nY2poBHBvcwM5BHNlYwN5bi1yLWItcmlnaHQEc2xrA3ZpZC11bi1saW5r/*http://news.yahoo.com/video/business-15749628/21680501" class="showtt" rel=":cnbc:20100831:av_cnbc:_yahoonewsvideo_business1579300283__21680498"> - <strong>Business Video:</strong> - JPM to Eliminate Commodities Prop Trading Group </a> - - <cite> - <a href="/i/3245;_ylt=A2KIKvTWiH1Ml3sBEgNu.aF4;_ylu=X3oDMTE4bWpuZDFiBHBvcwMxMARzZWMDeW4tci1iLXJpZ2h0BHNsawN2aWQtdW4tcHJvdmk-" >CNBC</a> - </cite> - </li> - </ul> - - </div> - </div><!-- end .related --> - - - -<div id="ygs-comments" class="ysp-mod"> - <a name="comments"></a> - - - - <div id="mwpphu-container" class="mwpphu-container"> <div class="mwpphu-header"> - <div class="mwpphu-left"> - <h3 id="mwpphu-stream-header" class="mwpphu-comments mwpphu-count-20 mwpphu-cmt-disabled-N ">20 Comments</h3> - </div> - <div class="mwpphu-right"> - <form method="get" action="/news/common/maple/en-US/mwphucmtnojssort" id="mwp-phugc-sort-form"> - <label id="mwp-phugc-sort-form-option-lable" for="mwp-phugc-sort-form-option">Show:</label> - <select name="SortCntrl" id="mwp-phugc-sort-form-option"> - <option selected value="/news/common/maple/en-US/mwphucmtgetcmt/headcontent/main/nmus_gm_china//date/desc/1/0">Newest First</option><option value="/news/common/maple/en-US/mwphucmtgetcmt/headcontent/main/nmus_gm_china//date/asc/1/0">Oldest First</option><option value="/news/common/maple/en-US/mwphucmtgetcmt/headcontent/main/nmus_gm_china//num_rating_up/desc/1/0">Highest Rated</option><option value="/news/common/maple/en-US/mwphucmtgetcmt/headcontent/main/nmus_gm_china//reply_count/desc/1/0">Most Replied</option> - </select> - <input type="hidden" name="basepage" value="http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china"> - <input type="hidden" name="hdcntid" value="nmus_gm_china"> - <noscript> - <button type="submit">Sort</button> - </noscript> - </form> - </div> - </div><div class="mwpphu-navigation"> - <div class="mwpphu-left"> - <div class="mwpphu-sprite_bg mwpphu-post-comments mwpphu-sprite_img"></div> - &nbsp;<a id="mwpphu-postcommentinternal" href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china">Post a Comment</a> - </div><div class="mwpphu-right imageloader_classname"> - <ul id="mwpphu-paging" class="mwpphu-ul_links"> - <li id="mwpphu-label-paging" class="mwpphu-first">Comments 1 - 10 of 20</li><li><a class="mwpphu-disabled" id="-date-desc-p-1-0" href="#">First</a></li><li><a class="mwpphu-disabled" id="-date-desc-p--9-0" href="#"> <span class="mwpphu-sprite_bg prev_arrow_off mwpphu-disabled"></span>Prev</a></li><li><a class="mwpphu-older" id="-date-desc-p-11-s24768460" href="http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china?cmtnav=/mwphucmtgetnojspage/headcontent/main/nmus_gm_china//date/desc/11/s24768460">Next<span class="mwpphu-sprite_bg next_arrow_on"></span></a></li><li class="mwpphu-last"><a class="mwpphu-oldest" id="-date-asc-p-1-0" href="http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china?cmtnav=/mwphucmtgetnojspage/headcontent/main/nmus_gm_china//date/asc/1/0">Last</a></li></ul></div></div><ul class="mwpphu-comments"> - <li id="mwpphu-comment-24826083" > <div id="mwpphu-replycount-24826083-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_XXKBYU5UNYCU564247GM6T6LGU"> - - <img id="com_24826083_XXKBYU5UNYCU564247GM6T6LGU" class="imageloader_classname" width="48" height="48" alt="Harley" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/a/i/identity/nopic_48.gif);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24826083" class=" mwpphu-rating-positive mwpphu-voted">1</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24826083" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24826083" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24826083" class=" mwpphu-rating-negetive mwpphu-rating-zero">0</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_XXKBYU5UNYCU564247GM6T6LGU"><strong>Harley</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T18:39:06-0700">4 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=XXKBYU5UNYCU564247GM6T6LGU&commentid=24826083" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">GM you may as well move to China, your done here. You have progressively ruined the top car maker in the USA. good job jackholes.</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24817915" > <div id="mwpphu-replycount-24817915-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_NGU3DAAIHVPK6K3B7Z323X6IXM"> - - <img id="com_24817915_NGU3DAAIHVPK6K3B7Z323X6IXM" class="imageloader_classname" width="48" height="48" alt="Jack" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/a/i/identity/nopic_48.gif);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24817915" class=" mwpphu-rating-positive mwpphu-rating-zero">0</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24817915" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24817915" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24817915" class=" mwpphu-rating-negetive mwpphu-rating-zero">0</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_NGU3DAAIHVPK6K3B7Z323X6IXM"><strong>Jack</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T17:51:52-0700">5 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=NGU3DAAIHVPK6K3B7Z323X6IXM&commentid=24817915" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">China is non union. Export Volt to the USA.</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24810979" > <div id="mwpphu-replycount-24810979-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_Y4T2AEEY6TUFWOA3XVOJX6YAZU"> - - <img id="com_24810979_Y4T2AEEY6TUFWOA3XVOJX6YAZU" class="imageloader_classname" width="48" height="48" alt="tommy" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/a/i/identity/nopic_48.gif);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24810979" class=" mwpphu-rating-positive mwpphu-voted">1</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24810979" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24810979" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24810979" class=" mwpphu-rating-negetive mwpphu-rating-zero">0</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_Y4T2AEEY6TUFWOA3XVOJX6YAZU"><strong>tommy</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T17:10:46-0700">5 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=Y4T2AEEY6TUFWOA3XVOJX6YAZU&commentid=24810979" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">so the american tax payer is going to subsidize the car for the chinese to buy. wonderful.</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24794015" > <div id="mwpphu-replycount-24794015-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_CEO3GYZ2F6QF3Z5XPMYP7DAZU4"> - - <img id="com_24794015_CEO3GYZ2F6QF3Z5XPMYP7DAZU4" class="imageloader_classname" width="48" height="48" alt="Robert" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://a323.yahoofs.com/coreid/4bbcf58ei107fzws124sp2/uquzEisjc6mffeSaJrqlxA--/2/tn48.jpeg?ciAQ2PNBkX4.6ohX);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24794015" class=" mwpphu-rating-positive mwpphu-voted">3</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24794015" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24794015" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24794015" class=" mwpphu-rating-negetive mwpphu-voted-neg">1</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_CEO3GYZ2F6QF3Z5XPMYP7DAZU4"><strong>Robert</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T15:23:27-0700">7 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=CEO3GYZ2F6QF3Z5XPMYP7DAZU4&commentid=24794015" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">GM moved their Buick City Complex to China at least 15 years ago guess they maybe able to sell something there as they are done here</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24788522" > <div id="mwpphu-replycount-24788522-1" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_4H4KUDNLRF4YSRCJTOJTINF534"> - - <img id="com_24788522_4H4KUDNLRF4YSRCJTOJTINF534" class="imageloader_classname" width="48" height="48" alt="Tobor" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://a323.yahoofs.com/coreid/4c48a29di26fbzws108mud/BS2XGmU7fugltQorMRQx6mw-/5/tn48.jpeg?ciAQ2PNBDDI0vdq7);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24788522" class=" mwpphu-rating-positive mwpphu-voted">5</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24788522" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24788522" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24788522" class=" mwpphu-rating-negetive mwpphu-voted-neg">3</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_4H4KUDNLRF4YSRCJTOJTINF534"><strong>Tobor</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T14:46:33-0700">8 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=4H4KUDNLRF4YSRCJTOJTINF534&commentid=24788522" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">Don&#39;t buy GM, they haven&#39;t made a quality vehicle in decades. Junk just plain junk.</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24787231" > <div id="mwpphu-replycount-24787231-1" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_ARZRO4V5HELZ3YDPNSWHGGYYLI"> - - <img id="com_24787231_ARZRO4V5HELZ3YDPNSWHGGYYLI" class="imageloader_classname" width="48" height="48" alt="meltrobe" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/dg/users/13TebVORTAAIC3wTYyWU=.medium.png);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24787231" class=" mwpphu-rating-positive mwpphu-voted">2</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24787231" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24787231" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24787231" class=" mwpphu-rating-negetive mwpphu-voted-neg">7</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_ARZRO4V5HELZ3YDPNSWHGGYYLI"><strong>meltrobe</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T14:37:09-0700">8 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=ARZRO4V5HELZ3YDPNSWHGGYYLI&commentid=24787231" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">That is only because the Chinese people don&#39;t know it is a product of obamamotors, or government motors. If they couldn&#39;t sell the car overseas, then the car would not sell. I will not buy an obamamotors car. I am looking for safety and will not support government motors in any way. Just kind of curious here, we have not heard anything else about unhealthy obamacare. Why build an death trap like the volt, when the messiah is trying to keep people healthy so his obamacare would work? Why did I call it a death trap? It is made by government motors and obamamotors. Enough said. Only the sand headed liberals would still support the lord god almighty obumbles including buying an obamamotors &quot;car.&quot;</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24783016" > <div id="mwpphu-replycount-24783016-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_EOJUFRRDCTVV3X7QW7ZJCU524U"> - - <img id="com_24783016_EOJUFRRDCTVV3X7QW7ZJCU524U" class="imageloader_classname" width="48" height="48" alt="Mr. Happy" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/dg/users/1Gmb3Mtb3AAECQwFOrAoPNJN0Eg==.medium.png);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24783016" class=" mwpphu-rating-positive mwpphu-voted">6</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24783016" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24783016" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24783016" class=" mwpphu-rating-negetive mwpphu-voted-neg">2</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_EOJUFRRDCTVV3X7QW7ZJCU524U"><strong>Mr. Happy</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T14:07:55-0700">8 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=EOJUFRRDCTVV3X7QW7ZJCU524U&commentid=24783016" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">GM is no longer a relevant company. It will continue to die a slow dead until all support is taken away. May GM RIP.</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24782720" > <div id="mwpphu-replycount-24782720-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_6IK64OMWDHK7SDQ7FTVD2R2HQM"> - - <img id="com_24782720_6IK64OMWDHK7SDQ7FTVD2R2HQM" class="imageloader_classname" width="48" height="48" alt="Robot B9" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://a323.yahoofs.com/coreid/4baa09edi2a89zws121ac4/cr9AZCM2cqSo4CdBON86_e0-/1/tn48.jpeg?ciAQ2PNBCzCHRpQV);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24782720" class=" mwpphu-rating-positive mwpphu-voted">2</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24782720" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24782720" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24782720" class=" mwpphu-rating-negetive mwpphu-rating-zero">0</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_6IK64OMWDHK7SDQ7FTVD2R2HQM"><strong>Robot B9</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T14:05:50-0700">8 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=6IK64OMWDHK7SDQ7FTVD2R2HQM&commentid=24782720" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">Why are they comparing the Leaf to the Volt?<br /> -<br /> -Leaf is just a plug in electric car, it has limited range.<br /> -<br /> -Volt is an electric car with a built in gasoline engine driven electrical generator. It has unlimited range.</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24775862" > <div id="mwpphu-replycount-24775862-0" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_73F2DIYPIPFJDINWROBTWW7ZBA"> - - <img id="com_24775862_73F2DIYPIPFJDINWROBTWW7ZBA" class="imageloader_classname" width="48" height="48" alt="dailybigot.com" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/a/i/identity/nopic_48.gif);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24775862" class=" mwpphu-rating-positive mwpphu-voted">4</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24775862" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24775862" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24775862" class=" mwpphu-rating-negetive mwpphu-voted-neg">1</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_73F2DIYPIPFJDINWROBTWW7ZBA"><strong>dailybigot.com</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T13:14:05-0700">9 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=73F2DIYPIPFJDINWROBTWW7ZBA&commentid=24775862" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">Volt will be a failure in China, Leaf will still be alot cheaper. GM isnt the brightest group</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li> - <li id="mwpphu-comment-24768460" > <div id="mwpphu-replycount-24768460-1" class="mwpphu-comment "><div class="mwpphu-avatar"><a href="http://pulse.yahoo.com/_6BAYU7XL5HNGGVRR2ETVU2HOI4"> - - <img id="com_24768460_6BAYU7XL5HNGGVRR2ETVU2HOI4" class="imageloader_classname" width="48" height="48" alt="Alfred" src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" style="background:url(http://l.yimg.com/a/i/identity/nopic_48.gif);"></a></div><!-- end .avatar --> - <div class="mwpphu-info"> - - <div class="mwpphu-rate"> - <em id="cmt-rating-pos-24768460" class=" mwpphu-rating-positive mwpphu-voted">1</em> <span>users liked this comment</span> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_up_lout mwpphu-sprite_img" id="cmt-rate-tu_24768460" title="Please sign in to rate!">Please sign in to rate this comment up.</a> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" class="mwpphu-sprite_bg blue_thumb_down_lout mwpphu-sprite_img" id="cmt-rate-td_24768460" title="Please sign in to rate!">Please sign in to rate this comment down.</a> - <em id="cmt-rating-neg-24768460" class=" mwpphu-rating-negetive mwpphu-voted-neg">3</em> <span>users disliked this comment</span></div><cite> <a target="_blank" href="http://pulse.yahoo.com/_6BAYU7XL5HNGGVRR2ETVU2HOI4"><strong>Alfred</strong></a> <span class="mwpphu-timestamp"><abbr title="2010-08-31T12:04:13-0700">10 hours ago</abbr></span> <a target="mwphcomReportAbuse" href="http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html?eaci=6BAYU7XL5HNGGVRR2ETVU2HOI4&commentid=24768460" class=" abuse ">Report Abuse</a> </cite><blockquote class="mwpphu-commenttext">It is my opinion that it cost them 1k per car and profit of 39k per car in up front cost then<br /> -that interest for 7 years to the banks whew! 100k for a car? GET REAL GM!</blockquote></div><!-- end .mwpphu-info --></div><!-- end .mwpphu-bd --></li></ul><div class="mwpphu-navigation"><div class="mwpphu-right imageloader_classname"> - <ul id="mwpphu-pagingcbottom" class="mwpphu-ul_links"> - <li id="mwpphu-label-pagingcbottom" class="mwpphu-first">Comments 1 - 10 of 20</li><li><a class="mwpphu-disabled" id="-date-desc-p-1-0" href="#">First</a></li><li><a class="mwpphu-disabled" id="-date-desc-p--9-0" href="#"> <span class="mwpphu-sprite_bg prev_arrow_off mwpphu-disabled"></span>Prev</a></li><li><a class="mwpphu-older" id="-date-desc-p-11-s24768460" href="http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china?cmtnav=/mwphucmtgetnojspage/headcontent/main/nmus_gm_china//date/desc/11/s24768460">Next<span class="mwpphu-sprite_bg next_arrow_on"></span></a></li><li class="mwpphu-last"><a class="mwpphu-oldest" id="-date-asc-p-1-0" href="http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china?cmtnav=/mwphucmtgetnojspage/headcontent/main/nmus_gm_china//date/asc/1/0">Last</a></li></ul></div></div></div> <div class="mwpphu-post-form imageloader_classname " id="mwpphu-post-form"> - <h3 class="mwpphu-title mwpphu-comments">Post a Comment</h3> - <a href="http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china%23mwpphu-post-form"> Sign in </a> to post a comment, or <a href="http://edit.yahoo.com/config/eval_register?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china%23mwpphu-post-form"> Sign up </a> for a free account. - </div> - - -</div> - - - - - - -<div class="mod mod3"> - <div class="yui-gb yui-gb1"> - <div class="yui-u first"> - <div id="yn-channel" class="mod ult-section mod-first"> - <div class="hd"> - <h3>Most Viewed - Business</h3> - </div> - <div class="bd"> - <ul class="list list4"> - <li class="ult-position first"> - <a href="/s/ap/20100831/ap_on_bi_ge/us_gettysburg_casino;_ylt=A2KIKvTWiH1Ml3sBEwNu.aF4;_ylu=X3oDMTE1ZGhybGp1BHBvcwMxBHNlYwN5bi1jaGFubmVsBHNsawNkZXZlbG9wZXJnZXQ-" class="showtt" rel=":ap:20100831:ap_on_bi_ge:us_gettysburg_casino">Developer: Gettysburg, casino can work together</a> - <cite>AP</cite> - </li> - <li class="ult-position"> - <a href="/s/ap/20100831/ap_on_bi_ge/us_colo_pot_farm;_ylt=A2KIKvTWiH1Ml3sBFANu.aF4;_ylu=X3oDMTE1aWg2MmFvBHBvcwMyBHNlYwN5bi1jaGFubmVsBHNsawNwcm9wb3NlZHBvdGY-" class="showtt" rel=":ap:20100831:ap_on_bi_ge:us_colo_pot_farm">Proposed pot farm on countryside angers residents</a> - <cite>AP</cite> - </li> - <li class="ult-position"> - <a href="/s/ap/20100831/ap_on_bi_ge/us_home_prices;_ylt=A2KIKvTWiH1Ml3sBFQNu.aF4;_ylu=X3oDMTE1bDZyam00BHBvcwMzBHNlYwN5bi1jaGFubmVsBHNsawNob21lcHJpY2Vzcmk-" class="showtt" rel=":ap:20100831:ap_on_bi_ge:us_home_prices">Home prices rise in 17 cities in June</a> - <cite>AP</cite> - </li> - <li class="ult-position"> - <a href="/s/ap/20100831/ap_on_bi_ge/us_auto_sales_fewer_discounts;_ylt=A2KIKvTWiH1Ml3sBFgNu.aF4;_ylu=X3oDMTE1OWhydWlhBHBvcwM0BHNlYwN5bi1jaGFubmVsBHNsawNub2RlYWxidXllcnM-" class="showtt" rel=":ap:20100831:ap_on_bi_ge:us_auto_sales_fewer_discounts">No deal: buyers will see fewer discounts for cars</a> - <cite>AP</cite> - </li> - <li class="ult-position"> - <a href="/s/ap/20100831/ap_on_bi_ge/us_us_china_trade;_ylt=A2KIKvTWiH1Ml3sBFwNu.aF4;_ylu=X3oDMTE1NW85N280BHBvcwM1BHNlYwN5bi1jaGFubmVsBHNsawN1c2ZpbmRzY2hpbmE-" class="showtt" rel=":ap:20100831:ap_on_bi_ge:us_us_china_trade">US finds China unfairly helped aluminum industry</a> - <cite>AP</cite> - </li> - </ul> - <a href="/most-popular/viewed/business;_ylt=A2KIKvTWiH1Ml3sBGANu.aF4;_ylu=X3oDMTE1NjB2OGt1BHBvcwM2BHNlYwN5bi1jaGFubmVsBHNsawNhbGxtb3N0dmlld2U-" class="more size1">All Most Viewed&nbsp;&raquo;</a> - </div> -</div> - - - - </div> - <div class="yui-u"> - -<div id="yn-daily-features" class="mod ult-section mod-first"> - <div class="hd"> - <h3>Daily Features</h3> - </div> - <div class="bd"> - <ul class="list list2 size1"> - <li class="first"> - <a href="/comics/100831/cx_monty_umedia/20103108;_ylt=A2KIKvTWiH1Ml3sBGQNu.aF4;_ylu=X3oDMTE1ZTQ4amFlBHBvcwMxBHNlYwN5bl9kYWlseV9mZWF0dXJlcwRzbGsDY29taWM-" ><img src="http://l.yimg.com/a/i/us/nws/2008/news/us/assets/common/images/transparent.png" width="201" height="63" alt="Comic" class="lzbg" style="background-image: url(http://d.yimg.com/b/util/anysize/200x-1209600,http%3A%2F%2Fd.yimg.com%2Fa%2Fp%2Fumedia%2F20100831%2Fcp.00296adbac1c491b9ff269efb90160f7.gif%3Fx%3D201%26y%3D63%26q%3D85%26sig%3DWpUk6UvvFvSVjIEsUtjIzQ--);"></a> - <a href="/comics;_ylt=A2KIKvTWiH1Ml3sBGgNu.aF4;_ylu=X3oDMTFjdTF2YTEwBHBvcwMyBHNlYwN5bl9kYWlseV9mZWF0dXJlcwRzbGsDYWxsY29taWNzcmFx" class="more">All Comics &raquo;</a> - </li> - <li> - <h4>Opinions &amp; Editorials: <a href="/i/742;_ylt=A2KIKvTWiH1Ml3sBGwNu.aF4;_ylu=X3oDMTFjdWVubGoxBHBvcwMzBHNlYwN5bl9kYWlseV9mZWF0dXJlcwRzbGsDZGl2ZXJzZXZpZXdz" >Diverse views on news from the right, left, and center.</a></h4> - <a href="/i/742;_ylt=A2KIKvTWiH1Ml3sBHANu.aF4;_ylu=X3oDMTFjZ3V0OWNqBHBvcwM0BHNlYwN5bl9kYWlseV9mZWF0dXJlcwRzbGsDYWxsb3BpbmlvbnJh" class="more">All Opinion &raquo;</a> -</li> <!-- --> </ul> - </div> -</div> - - <div class="ad-links mod"> - <div id="darla-ad__REC" class="ft darla_ad"></div> - </div> - - - </div> - <div class="yui-u last"> - - <div id="yn-elseweb" class="mod ult-section mod-first"> - <div class="hd"> - <h3>Elsewhere on the Web</h3> - </div> - <div class="bd"> - <ul class="list"> - <li class="ult-position first"> - <a href="http://us.rd.yahoo.com/dailynews/external/time_rss/rss_time_bs/httpwwwtimecomtimebusinessarticle08599201368400htmlxidrssbiztechyahoo/37347610/SIG=12o1o71cf;_ylt=A2KIKvTWiH1Ml3sBHQNu.aF4;_ylu=X3oDMTEwdHFsMHBnBHBvcwMxBHNlYwN5bi1lbHNld2ViBHNsawN0aW1lY29t/*http://www.time.com/time/business/article/0,8599,2013684,00.html?xid=rss-biztech-yahoo" ><strong>Time.com: </strong>After Housing Bubble, the Dark Side of Homeowner Dreams</a> - </li> - <li class="ult-position"> - <a href="http://us.rd.yahoo.com/dailynews/external/npr_rss/rss_npr_bs/httpwwwnprorgtemplatesstorystoryphpstoryid129559028ampft1ampf1006/37407849/SIG=12cdnun4d;_ylt=A2KIKvTWiH1Ml3sBHgNu.aF4;_ylu=X3oDMTBzZzE5c2diBHBvcwMyBHNlYwN5bi1lbHNld2ViBHNsawNucHI-/*http://www.npr.org/templates/story/story.php?storyId=129559028&ft=1&f=1006" ><strong>NPR: </strong>U.S.: China Unfairly Aided Aluminum Industry</a> - </li> - <li class="ult-position"> - <a href="http://us.rd.yahoo.com/dailynews/external/fox_rss/rss_fox_bs/httpwwwfoxbusinesscomstoryhomeofficehomeworkkidsfindingbalance/37355129/SIG=1239alusv;_ylt=A2KIKvTWiH1Ml3sBHwNu.aF4;_ylu=X3oDMTE0dTJjYzIzBHBvcwMzBHNlYwN5bi1lbHNld2ViBHNsawNmb3hidXNpbmVzcw--/*http://feeds.foxbusiness.com/~r/foxbusiness/yahoo/~3/8flC0jKI6Io/" ><strong>FOXBusiness: </strong>Home, Work, Kids and Finding Balance</a> - </li> - </ul> - </div> - </div> - - - - </div> - </div> -</div> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - </div><!-- end: .yui-b --> - - </div><!-- end: #yui-main --> - - <div id="sidebar" class="yui-b" role="complementary"> - - <div id="yn-top-stories" class="mod ult-section"> - <div class="hd"> - <h3>Top Stories</h3> - </div> - <div class="bd"> - <ul class="list list4 size2"> - <li class="first "><a href="/s/ap/20100831/ap_on_hi_te/us_amazon_subscription_video;_ylt=A2KIKvTWiH1Ml3sBIANu.aF4;_ylu=X3oDMTNobzQwN2MzBGFzc2V0A2FwLzIwMTAwODMxL3VzX2FtYXpvbl9zdWJzY3JpcHRpb25fdmlkZW8EY2NvZGUDbW9zdHBvcHVsYXIEY3BvcwMxBHBvcwMxBHNlYwN5bl90b3Bfc3RvcmllcwRzbGsDYW1hem9udHJ5aW5n" class="showtt yltasis" rel=":ap:20100831:ap_on_hi_te:us_amazon_subscription_video"> Amazon trying to offer subscription TV, movies<!-- COKE --></a></li><li class=" "><a href="/s/usnews/20100831/ts_usnews/5upgradesthatwasteyourmoney;_ylt=A2KIKvTWiH1Ml3sBIQNu.aF4;_ylu=X3oDMTNrdmtuMGppBGFzc2V0A3VzbmV3cy8yMDEwMDgzMS81dXBncmFkZXN0aGF0d2FzdGV5b3VybW9uZXkEY2NvZGUDbW9zdHBvcHVsYXIEY3BvcwMyBHBvcwMyBHNlYwN5bl90b3Bfc3RvcmllcwRzbGsDNXVwZ3JhZGVzdGhh" class="showtt yltasis" rel=":usnews:20100831:ts_usnews:5upgradesthatwasteyourmoney"> 5 upgrades that aren't worth the money<!-- COKE --></a></li><li class=" "><a href="/s/hsn/20100831/hl_hsn/uspediatriciansdecrymediasportrayalofsex;_ylt=A2KIKvTWiH1Ml3sBIgNu.aF4;_ylu=X3oDMTN1NmI3bzlkBGFzc2V0A2hzbi8yMDEwMDgzMS91c3BlZGlhdHJpY2lhbnNkZWNyeW1lZGlhc3BvcnRyYXlhbG9mc2V4BGNjb2RlA21vc3Rwb3B1bGFyBGNwb3MDMwRwb3MDMwRzZWMDeW5fdG9wX3N0b3JpZXMEc2xrA3BlZGlhdHJpY2lhbg--" class="showtt yltasis" rel=":hsn:20100831:hl_hsn:uspediatriciansdecrymediasportrayalofsex"> Pediatricians' group decry media's portrayal of sex<!-- COKE --></a></li><li class=" "><a href="/s/space/20100831/sc_space/deadspacecraftonmarslivesoninnewstudy;_ylt=A2KIKvTWiH1Ml3sBIwNu.aF4;_ylu=X3oDMTN0cTg2Z2tlBGFzc2V0A3NwYWNlLzIwMTAwODMxL2RlYWRzcGFjZWNyYWZ0b25tYXJzbGl2ZXNvbmlubmV3c3R1ZHkEY2NvZGUDbW9zdHBvcHVsYXIEY3BvcwM0BHBvcwM0BHNlYwN5bl90b3Bfc3RvcmllcwRzbGsDZGVhZHNwYWNlY3Jh" class="showtt yltasis" rel=":space:20100831:sc_space:deadspacecraftonmarslivesoninnewstudy"> Dead Spacecraft on Mars Lives on in New Study<!-- COKE --></a></li><li class=" "><a href="/s/ac/20100831/tr_ac/6698988_how_many_attended_glenn_becks_rally_estimates_vary_widely;_ylt=A2KIKvTWiH1Ml3sBJANu.aF4;_ylu=X3oDMTRtNWZyMmVwBGFzc2V0A2FjLzIwMTAwODMxLzY2OTg5ODhfaG93X21hbnlfYXR0ZW5kZWRfZ2xlbm5fYmVja3NfcmFsbHlfZXN0aW1hdGVzX3Zhcnlfd2lkZWx5BGNjb2RlA21vc3Rwb3B1bGFyBGNwb3MDNQRwb3MDNQRzZWMDeW5fdG9wX3N0b3JpZXMEc2xrA251bWJlcnNnYW1laA--" class="showtt yltasis" rel=":ac:20100831:tr_ac:6698988_how_many_attended_glenn_becks_rally_estimates_vary_widely"> Numbers game: How many attended Glenn Beck's rally?<!-- COKE --></a></li><li class=" "><a href="/s/nm/20100831/bs_nm/us_facebook;_ylt=A2KIKvTWiH1Ml3sBJQNu.aF4;_ylu=X3oDMTMwZDR1amN2BGFzc2V0A25tLzIwMTAwODMxL3VzX2ZhY2Vib29rBGNjb2RlA21vc3Rwb3B1bGFyBGNwb3MDNgRwb3MDNgRzZWMDeW5fdG9wX3N0b3JpZXMEc2xrA2ZhY2Vib29rY2Vvaw--" class="showtt yltasis" rel=":nm:20100831:bs_nm:us_facebook"> Facebook CEO: Keep private life out of lawsuit<!-- COKE --></a></li><li class=" "><a href="/s/ap/lt_drug_war_mexico;_ylt=A2KIKvTWiH1Ml3sBJgNu.aF4;_ylu=X3oDMTM3dWZmODAxBGFzc2V0A2FwLzIwMTAwODMxL2x0X2RydWdfd2FyX21leGljbwRjY29kZQNtb3N0cG9wdWxhcgRjcG9zAzcEcG9zAzcEc2VjA3luX3RvcF9zdG9yaWVzBHNsawNtZXhpY29jYXB0dXI-" class="showtt yltasis" rel=":ap:lt_drug_war_mexico"> Mexico captures reported drug lord 'the Barbie'<!-- COKE --></a></li><li class=" "><a href="/s/ap/us_drunk_driving_montana;_ylt=A2KIKvTWiH1Ml3sBJwNu.aF4;_ylu=X3oDMTNkZWQ3cjUzBGFzc2V0A2FwLzIwMTAwODMxL3VzX2RydW5rX2RyaXZpbmdfbW9udGFuYQRjY29kZQNtb3N0cG9wdWxhcgRjcG9zAzgEcG9zAzgEc2VjA3luX3RvcF9zdG9yaWVzBHNsawNtb250YW5hZHJpbms-" class="showtt yltasis" rel=":ap:us_drunk_driving_montana"> Montana drinking and driving culture at crossroads<!-- COKE --></a></li><li class=" "><a href="/s/yblog_upshot/20100831/bs_yblog_upshot/bp-acknowledges-oil-washing-ashore-on-florida-coast;_ylt=A2KIKvTWiH1Ml3sBKANu.aF4;_ylu=X3oDMTRpZHMzMnNzBGFzc2V0A3libG9nX3Vwc2hvdC8yMDEwMDgzMS9icC1hY2tub3dsZWRnZXMtb2lsLXdhc2hpbmctYXNob3JlLW9uLWZsb3JpZGEtY29hc3QEY2NvZGUDbW9zdHBvcHVsYXIEY3BvcwM5BHBvcwM5BHNlYwN5bl90b3Bfc3RvcmllcwRzbGsDYnBhY2tub3dsZWRn" class="showtt yltasis" rel=":yblog_upshot:20100831:bs_yblog_upshot:bp-acknowledges-oil-washing-ashore-on-florida-coast"> BP acknowledges oil washing ashore in Florida<!-- COKE --></a></li><li class=" "><a href="/s/nm/us_britain_sites;_ylt=A2KIKvTWiH1Ml3sBKQNu.aF4;_ylu=X3oDMTM3MTlua2k0BGFzc2V0A25tLzIwMTAwODMxL3VzX2JyaXRhaW5fc2l0ZXMEY2NvZGUDbW9zdHBvcHVsYXIEY3BvcwMxMARwb3MDMTAEc2VjA3luX3RvcF9zdG9yaWVzBHNsawNwYXJjaGVkZW5nbGk-" class="showtt yltasis" rel=":nm:us_britain_sites"> Parched English fields reveal ancient sites<!-- COKE --></a></li> </ul> - </div> - <div class="ft"> - <a href="http://news.yahoo.com/i/2372;_ylt=A2KIKvTWiH1Ml3sBKgNu.aF4;_ylu=X3oDMTFmcHZmMjl2BHBvcwMxMQRzZWMDeW5fdG9wX3N0b3JpZXNfY29rZQRzbGsDbW9yZXRvcHN0b3Jp" class="yltasis">More Top Stories &raquo;</a> - </div> -</div> - -<div id="darla-ad__LREC" class="mod ad darla_ad mod-first"></div> - - -<div id="sponsored-links" class="mod ult-section"> - <div class="hd"> - <h3>Sponsored Links</h3> - </div> - <div class="bd"> - <ul> - <li class="ult-position first"> - <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBKwNu.aF4;_ylu=X3oDMTB2aTdkdmQ5BHBvcwMxBHNlYwNvdi1iBHNsawMxNTAwMGJlYXV0aWY-/SIG=1a01gol0g/**http%3A//rc.us-west.srv.overture.com/d/sr/%3Fxargs=20AJpVDqKMlmVpjxtLazqO9iFNOIgWn8JEoCkfGrvjelX-TVG5mJPYQ6C3E8oAhz-UWheV_AZxLQPipai_6juCC9wXoghCzNKi-yI_oFtT_r9bEaXkDV3ygy1YtGs9U115Ka4yDyU466sxehjSW-xOMMUef6h4nzCHBcgNfaep8LAVp7ningOhKWeXY4D5OMOOyfRjHPyNX0Ry2XTua6KEi37keb0PZjC_9xGzrfifd5Cd4vQLVNsnYh8.00000001d6a6ae12" class="yltasis">15,000 Beautiful Brides</a> - Marry one of Stunning Russian Brides & Neighbors will Envy You. <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBLANu.aF4;_ylu=X3oDMTB2NWQzM2VtBHBvcwMyBHNlYwNvdi1iBHNsawNhbmFzdGFzaWFkYXQ-/SIG=1a01gol0g/**http%3A//rc.us-west.srv.overture.com/d/sr/%3Fxargs=20AJpVDqKMlmVpjxtLazqO9iFNOIgWn8JEoCkfGrvjelX-TVG5mJPYQ6C3E8oAhz-UWheV_AZxLQPipai_6juCC9wXoghCzNKi-yI_oFtT_r9bEaXkDV3ygy1YtGs9U115Ka4yDyU466sxehjSW-xOMMUef6h4nzCHBcgNfaep8LAVp7ningOhKWeXY4D5OMOOyfRjHPyNX0Ry2XTua6KEi37keb0PZjC_9xGzrfifd5Cd4vQLVNsnYh8.00000001d6a6ae12" class="host yltasis">AnastasiaDate.com</a> - </li> - <li class="ult-position"> - <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBLQNu.aF4;_ylu=X3oDMTB2NTFqbWQ2BHBvcwMzBHNlYwNvdi1iBHNsawNkYW5jZXJwb2xlc2Y-/SIG=1a0ufhoqr/**http%3A//rc.us-west.srv.overture.com/d/sr/%3Fxargs=20AJpVDqKMlmVpVXaz5MPbsStoiZn2fgXhGbGUPw5XsTe2chUQRkYeVPLmGzdI3A6rfQKRmnaEt_8hPrEZfv2QzqVDQdXcxebNStN8pC-eCkNjoZe3Tn-9_cyZtg2bJFEl7NbL-HNENIQ4ldwfX7gRyGt5fMyKkFjwhi0TAW_696XBtzMw9rCbCmKng2mMSLZVxk5RWBDF3RtexkGgzbi1ltTFITPUMeBF8Bm9Sh_NoIeE1Ys4LwC3FsQ.00000001d6a6ae12" class="yltasis">Dancer Poles for Pole Dancing Classes</a> - Completely Removable Spinning & Stationary Poles. Best Fitness Poles. <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBLgNu.aF4;_ylu=X3oDMTB2MHFrdjZyBHBvcwM0BHNlYwNvdi1iBHNsawNwbGF0aW51bXN0YWc-/SIG=1a0ufhoqr/**http%3A//rc.us-west.srv.overture.com/d/sr/%3Fxargs=20AJpVDqKMlmVpVXaz5MPbsStoiZn2fgXhGbGUPw5XsTe2chUQRkYeVPLmGzdI3A6rfQKRmnaEt_8hPrEZfv2QzqVDQdXcxebNStN8pC-eCkNjoZe3Tn-9_cyZtg2bJFEl7NbL-HNENIQ4ldwfX7gRyGt5fMyKkFjwhi0TAW_696XBtzMw9rCbCmKng2mMSLZVxk5RWBDF3RtexkGgzbi1ltTFITPUMeBF8Bm9Sh_NoIeE1Ys4LwC3FsQ.00000001d6a6ae12" class="host yltasis">PlatinumStages.com</a> - </li> - <li class="ult-position"> - <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBLwNu.aF4;_ylu=X3oDMTB2MmZ0Z28yBHBvcwM1BHNlYwNvdi1iBHNsawNtYWtleW91cmRyZWE-/SIG=1a0cil2hj/**http%3A//rc.us-west.srv.overture.com/d/sr/%3Fxargs=20AHu7MuzoRomvjxtLazqO9iFPCFwt543Frt1M2Uaou1eEavUEio8HsgxgLhEbC5KLNdH8moVgWLxIuebMhXVU3IuByb8pbLc650maDoxJAWjeAhLKn5sUFfEWqLAXAeBMYooR2YJDQHPejdXoserzZoV7-95wOiLQJMRVk0LSsia-MEO0JQPjp8EcN4y8yvUXPjdSogwPJdWqNhb_dbkOf03pEojj12Ochq8m6Uc17xdoB_D4oMP1FZs.00000001d6a6ae12" class="yltasis">Make Your Dreams Come True</a> - The spell will make your dream come true. Drive a F1 car? Go to space? <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBMANu.aF4;_ylu=X3oDMTB2MmRtZDUyBHBvcwM2BHNlYwNvdi1iBHNsawN3d3d2b29kb28tc3A-/SIG=1a0cil2hj/**http%3A//rc.us-west.srv.overture.com/d/sr/%3Fxargs=20AHu7MuzoRomvjxtLazqO9iFPCFwt543Frt1M2Uaou1eEavUEio8HsgxgLhEbC5KLNdH8moVgWLxIuebMhXVU3IuByb8pbLc650maDoxJAWjeAhLKn5sUFfEWqLAXAeBMYooR2YJDQHPejdXoserzZoV7-95wOiLQJMRVk0LSsia-MEO0JQPjp8EcN4y8yvUXPjdSogwPJdWqNhb_dbkOf03pEojj12Ochq8m6Uc17xdoB_D4oMP1FZs.00000001d6a6ae12" class="host yltasis">www.voodoo-spells.net</a> - </li> - </ul> - </div> -</div> - -<div id="yn-featured" class="mod ult-section"> - <div class="hd"> - <h3>Featured</h3> - </div> - <div class="bd"> - <ul> - <li class="first"><a href="http://news.yahoo.com/s/usnews/sixpopularcreditscoremyths;_ylt=A2KIKvTWiH1Ml3sBMQNu.aF4;_ylu=X3oDMTB2bW42Z3BoBHBvcwMxBHNlYwN5bl9mZWF0dXJlZARzbGsDaW1hZ2U-" ><img width="67" height="67" alt="" src="http://d.yimg.com/a/p/us/news/editorial/3/d8/3d8cd106b02884b0b941e342354f1c5d.jpeg"/></a><div><a href="http://news.yahoo.com/s/usnews/sixpopularcreditscoremyths;_ylt=A2KIKvTWiH1Ml3sBMgNu.aF4;_ylu=X3oDMTExdjRncm1iBHBvcwMyBHNlYwN5bl9mZWF0dXJlZARzbGsDbXltb25leQ--" >My Money</a>Six popular credit score myths.<a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBMwNu.aF4;_ylu=X3oDMTE2N2sxbWs0BHBvcwMzBHNlYwN5bl9mZWF0dXJlZARzbGsDcmFxdW9tb3JlZnJv/SIG=11n913fac/**http%3A//www.usnews.com/sections/business/index.html" style="font-size:13px;font-weight:normal;">&raquo; More from U.S. News</a></div></li><li><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBNANu.aF4;_ylu=X3oDMTB2MHM0b25wBHBvcwM0BHNlYwN5bl9mZWF0dXJlZARzbGsDaW1hZ2U-/SIG=113m54hi3/**http%3A//whoknew.news.yahoo.com/" ><img width="67" height="67" alt="" src="http://l.yimg.com/a/p/us/news/editorial/2/72/272a96bc7fcd700e349fb735871ba1d4.jpeg" /></a> -<div><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBNQNu.aF4;_ylu=X3oDMTE2ZWM5cWFyBHBvcwM1BHNlYwN5bl9mZWF0dXJlZARzbGsDd2F0Y2h3aG9rbmV3/SIG=113m54hi3/**http%3A//whoknew.news.yahoo.com/" >Watch Who Knew?:</a>There's a new way to get the news.<a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBNgNu.aF4;_ylu=X3oDMTE2bjFzb2cyBHBvcwM2BHNlYwN5bl9mZWF0dXJlZARzbGsDcmFxdW9tb3Jld2hv/SIG=1173hj19n/**http%3A//www.whoknew.news.yahoo.com/" style="font-size:13px;font-weight:normal;">&raquo; More Who Knew? videos</a></div></li><li><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBNwNu.aF4;_ylu=X3oDMTB2djk2ZDM1BHBvcwM3BHNlYwN5bl9mZWF0dXJlZARzbGsDaW1hZ2U-/SIG=116ptbs2m/**http%3A//www.facebook.com/yahoonews" ><img width="67" height="67" alt="" src="http://l.yimg.com/a/p/us/news/editorial/5/f3/5f375d877ed1a63852b40a6688f9851f.jpeg"/></a><div><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBOANu.aF4;_ylu=X3oDMTE2MTA0OTZqBHBvcwM4BHNlYwN5bl9mZWF0dXJlZARzbGsDdG9wc3Rvcmllc29u/SIG=116ptbs2m/**http%3A//www.facebook.com/yahoonews" >Top Stories on Facebook</a>Are you on Facebook? Join our page for top stories! <a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBOQNu.aF4;_ylu=X3oDMTE2MjE4cG1nBHBvcwM5BHNlYwN5bl9mZWF0dXJlZARzbGsDcmFxdW9qb2lub3Vy/SIG=116ptbs2m/**http%3A//www.facebook.com/yahoonews" style="font-size:13px;font-weight:normal;">&raquo; Join our Facebook page here</a></div></li><li><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBOgNu.aF4;_ylu=X3oDMTEwdWJkajJmBHBvcwMxMARzZWMDeW5fZmVhdHVyZWQEc2xrA2ltYWdl/SIG=1116s09pc/**http%3A//twitter.com/YahooNews" ><img width="67" height="67" alt="" src="http://l.yimg.com/a/p/us/news/editorial/e/ca/ecab1dd44ac4d1bdb647e2071d0647c4.jpeg"/></a><div><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBOwNu.aF4;_ylu=X3oDMTE3ZjZ0MGxnBHBvcwMxMQRzZWMDeW5fZmVhdHVyZWQEc2xrAzEwMHJzc2ZlZWRmcg--/SIG=1116s09pc/**http%3A//twitter.com/YahooNews" >100% RSS feed free</a>Follow Yahoo! News on Twitter to get the top stories.<a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBPANu.aF4;_ylu=X3oDMTE3YzV0Zjh0BHBvcwMxMgRzZWMDeW5fZmVhdHVyZWQEc2xrA3JhcXVvZm9sbG93eQ--/SIG=1116s09pc/**http%3A//twitter.com/YahooNews" style="font-size:13px;font-weight:normal;">&raquo; Follow @YahooNews here!</a></div></li><li><script type="text/javascript"> -if(typeof jQuery==='undefined') - document.write(unescape('%3Cscript type="text/javascript" src="http://l.yimg.com/d/lib/news/p/common/generic/jquery_1.4.2-min-42268.js"%3E%3C/script%3E')); -</script> - -<script type="text/javascript"> -$(document).ready(function() { -$("#yn-featured li:visible:last").css("display","none"); - -<!-- EDIT HERE ONLY --> - -var slideshow_link1 = 'http://news.yahoo.com/nphotos/Hurricane-Earl/ss/events/sc/083010hurricaneearl'; -var img_headline1 = "Earl threatens U.S. after hitting Caribbean"; -var embedded_img_link1 = 'http://d.yimg.com/a/p/ap/20100830/capt.02e901574d9a4dad89a4326464d2618b-02e901574d9a4dad89a4326464d2618b-0.jpg?x=400&y=294&q=85&sig=SWB8GMwH5tZYVL47HXZlHg--'; - - - -var slideshow_link2 = 'http://news.yahoo.com/nphotos/Oval-Office-Renovations/ss/events/pl/083110ovaloffice'; -var img_headline2 = "Oval Office makeover has comfy, modern feel"; -var embedded_img_link2 = 'http://d.yimg.com/a/p/ap/20100831/capt.dc28a8f07c354757aec6ee9c5fab1dda-dc28a8f07c354757aec6ee9c5fab1dda-0.jpg?x=400&y=251&q=85&sig=6PXpnAHLEGsbQvEL0qT4JA--'; - - - -var slideshow_link3 = 'http://news.yahoo.com/nphotos/Congo-Nyiragongo-Volcano/ss/events/sc/083110nyiragongo'; -var img_headline3 = "Congo lava lake's glowing light show"; -var embedded_img_link3 = 'http://d.yimg.com/a/p/net/20100831/capt.e5247ce5647223f8612059ce22e09202.jpeg?x=400&y=266&q=85&sig=344S0xwMoqDue.VjK8MUaw--'; - - - -var slideshow_link4 = 'http://news.yahoo.com/nphotos/Photo-Highlight/ss/441'; -var img_headline4 = "Photo Highlight of the Day"; -var embedded_img_link4 = 'http://d.yimg.com/a/p/rids/20100831/i/r2049720478.jpg?x=400&y=300&q=85&sig=tAX8K0Hosk3T1kxkdMGDEQ--'; - - - -<!-- DO NOT EDIT BELOW THIS LINE --> - -// turn off editor's pick -// when the varible is not present, it should default to show -if (typeof (YNEWS_EdPick) !== 'undefined' && YNEWS_EdPick=== '0') { return;}; - -var Str_EdPick = '<br /><div style="width:640px; height:15px; font:bold 16px arial; color:#314251; padding:10px; background-color:#fafafa; border-top:1px solid #dcdcdc; border-bottom:1px solid #ddd; margin-bottom:10px;"><div style="float:left; width:60%;"><a href="http://news.yahoo.com/photos;_ylt=A2KIKvTWiH1Ml3sBPQNu.aF4;_ylu=X3oDMTB1M3M4cDdkBHBvcwMxMwRzZWMDZXAEc2xrA2VkaXRvcnNwaWNrcw--" style="color:#314251;">Editor\'s Picks</a></div><div style="padding-top:3px; width:40%; text-align:right; float:left; color:#0058a6; font:11px arial; font-weight:bold;" class="photostrip_link"><a href="http://news.yahoo.com/photos;_ylt=A2KIKvTWiH1Ml3sBPgNu.aF4;_ylu=X3oDMTB1MXZoaWc4BHBvcwMxNARzZWMDZXAEc2xrA21vcmVzbGlkZXNobw--" >More Slideshows &raquo;</a></div></div><div style="clear:both;"></div><div class="photostrip_container"><ul><li><a href="'+slideshow_link1+';_ylt=A2KIKvTWiH1Ml3sBPwNu.aF4;_ylu=X3oDMTBuaWdtdnI2BHBvcwMxNQRzZWMDZXAEc2xrA2ltYWdl" ><img src="' + embedded_img_link1 + '" /></a><div class="photostrip_link"><a href="'+slideshow_link1+';_ylt=A2KIKvTWiH1Ml3sBQANu.aF4;_ylu=X3oDMTB1MWdudjd2BHBvcwMxNgRzZWMDZXAEc2xrA2ltZ19oZWFkbGluZQ--" >' + img_headline1 + '</a></div></li><li><a href="'+slideshow_link2+';_ylt=A2KIKvTWiH1Ml3sBQQNu.aF4;_ylu=X3oDMTBuZzVjMHViBHBvcwMxNwRzZWMDZXAEc2xrA2ltYWdl" ><img src="' + embedded_img_link2 + '" /></a><div class="photostrip_link"><a href="'+slideshow_link2+';_ylt=A2KIKvTWiH1Ml3sBQgNu.aF4;_ylu=X3oDMTB1bjdxb3ZxBHBvcwMxOARzZWMDZXAEc2xrA2ltZ19oZWFkbGluZQ--" >' + img_headline2 + '</a></div></li><li><a href="'+slideshow_link3+';_ylt=A2KIKvTWiH1Ml3sBQwNu.aF4;_ylu=X3oDMTBubW10OGo5BHBvcwMxOQRzZWMDZXAEc2xrA2ltYWdl" ><img src="' + embedded_img_link3 + '" /></a><div class="photostrip_link"><a href="'+slideshow_link3+';_ylt=A2KIKvTWiH1Ml3sBRANu.aF4;_ylu=X3oDMTB1OWV1a2VhBHBvcwMyMARzZWMDZXAEc2xrA2ltZ19oZWFkbGluZQ--" >' + img_headline3 + '</a></div></li><li><a href="'+slideshow_link4+';_ylt=A2KIKvTWiH1Ml3sBRQNu.aF4;_ylu=X3oDMTBuMzc3aW1xBHBvcwMyMQRzZWMDZXAEc2xrA2ltYWdl" ><img src="' + embedded_img_link4 + '" /></a><div class="photostrip_link"><a href="'+slideshow_link4+';_ylt=A2KIKvTWiH1Ml3sBRgNu.aF4;_ylu=X3oDMTB1MmxnaGc2BHBvcwMyMgRzZWMDZXAEc2xrA2ltZ19oZWFkbGluZQ--" >' + img_headline4 + '</a></div></li></ul></div><div style="clear:both;"></div><br />'; - -// in some buckets the related content module is turned off -// in those cases, we append the editor's pick module after yn-story body instead -if (document.getElementById('yn-story-related-content')) { - $("#yn-story-related-content").before(Str_EdPick); -} else { - $("#yn-story").after(Str_EdPick); -}; - -$("div.photostrip_container li:first").css("margin-left","0"); - -}); -</script> - -<style type="text/css"> -.photostrip_link a { text-decoration:none; color:#0058a6; } -.photostrip_link a:hover { text-decoration: underline; } -.photostrip_container li { margin-left:11px; width:156px; float:left; font:13px arial; } -.photostrip_container ul { margin-left:0; padding-left:0; font:11px arial; } -.photostrip_container img { width:148px; height:97px; border:1px solid #000; padding:3px; margin-bottom:5px; } -</style></li><li><script type="text/javascript"> -if(typeof jQuery==='undefined') - document.write(unescape('%3Cscript type="text/javascript" src="http://l.yimg.com/d/lib/news/p/common/generic/jquery_1.4.2-min-42268.js"%3E%3C/script%3E')); -</script> - -<script type="text/javascript"> -$(document).ready(function() { -$("#yn-featured li:visible:last").css("display","none"); - - -<!------------------------------ Edit Below Here ----------------------> - -var photos = new Array(); -photos = [ - -<!----------------------------------------------------------------------------[ UPDATED: HURRICANE EARL 8/31 ]--> -[ [['Hurricane Earl', 9], ['Interests from the Carolinas northward to New England', 7]], - 'http://news.yahoo.com/nphotos/Hurricane-Earl/ss/events/sc/083010hurricaneearl', - "Click image to see Hurricane Earl photos", - 'http://d.yimg.com/a/p/ap/20100830/capt.02e901574d9a4dad89a4326464d2618b-02e901574d9a4dad89a4326464d2618b-0.jpg?x=400&y=294&q=85&sig=SWB8GMwH5tZYVL47HXZlHg--', - '399', - '294', - 'AP/Johnny Jno-Baptiste' -], -<!----------------------------------------------------------------------------[ UPDATED: GLENN BECK RALLY 8/26 ]--> -[ [['Restoring Honor', 5], ['Other orators include Beck himself', 4]], - 'http://news.yahoo.com/nphotos/Glenn-Beck-host-Lincoln-Memorial-rally/ss/events/us/082610beckrally', - "Click image to see photos of Glenn Beck\'s \'Restoring Honor\' rally", - 'http://d.yimg.com/a/p/ap/20100828/capt.40920b71907445f1aa65c15c0eed19ac-40920b71907445f1aa65c15c0eed19ac-0.jpg?x=400&y=266&q=85&sig=0nlzxlCOnRJj6zglAqbXEw--', - '399', - '266', - 'AP/Jacquelyn Martin' -], -<!----------------------------------------------------------------------------[ UPDATED: CHILE MINE ]--> -[ [['The thing that concerns me is welfare of workers', 9], ['We need to urgently establish what psychological situation', 8]], - 'http://news.yahoo.com/nphotos/Chile-Mine-Collapse/ss/events/us/082210chilemine', - "Click image to see photos from the Chile mine rescue efforts", - 'http://d.yimg.com/a/p/afp/20100831/capt.photo_1283186416477-1-0.jpg?x=400&y=300&q=85&sig=K6SJH_MWuvNc0l_X2PQb9Q--', - '400', - '320', - 'AFP' -], -<!----------------------------------------------------------------------------[ UPDATED: EMMY RED CARPET ]--> -[ [['updos and bold jewelry also were big trends', 8]], - 'http://news.yahoo.com/nphotos/Emmy-Awards-Red-Carpet-Arrivals/ss/events/en/082910emmysredcarpet', - "Click image to see red carpet photos", - 'http://d.yimg.com/a/p/ap/20100830/capt.f07a4c5dfcac471baa54b4f1595bd246-f07a4c5dfcac471baa54b4f1595bd246-0.jpg?x=400&y=287&q=85&sig=ZbwDkQgsCjAd72X3TUVmcA--', - '399', - '287', - 'AP/Matt Sayles' -], -<!----------------------------------------------------------------------------[ UPDATED: EMMY SHOW ]--> -[ [['Emmy had a split personality this year', 6]], - 'http://news.yahoo.com/nphotos/Emmy-Awards/ss/events/en/071708primetimeemmys', - "Click image to see photos from the Emmy Awards", - 'http://d.yimg.com/a/p/ap/20100830/capt.c7db8cc6c52041b580ca026e2f7fe0c1-c7db8cc6c52041b580ca026e2f7fe0c1-0.jpg?x=396&y=345&q=85&sig=GbDYOfzdaZcJ0jfgc6ZDeg--', - '396', - '345', - 'AP/Chris Carlson' -], -<!----------------------------------------------------------------------------[ UPDATED: PAKISTAN FLOODS ]--> -[ [['Nearly 60 percent of patients are suffering from gastroenteritis', 8]], - 'http://news.yahoo.com/nphotos/Pakistan-flooding-ravages-countryside-kills-hundreds/ss/events/wl/073010peshfloods', - "Click image to see photos of Pakistan's flood devastation", - 'http://d.yimg.com/a/p/ap/20100831/capt.f9d1472914a64e0dba7ce1669269b988-f9d1472914a64e0dba7ce1669269b988-0.jpg?x=400&y=240&q=85&sig=r3jnMW1awrz5hX22SZ6CbA--', - '400', - '240', - 'AP/Vincent Thian' -], -<!----------------------------------------------------------------------------[ UPDATED: INDONESIA VOLCANO 8/31 ]--> -[ [['Mount Sinabung', 8]], - 'http://news.yahoo.com/nphotos/Indonesia-Mount-Sinabung-erupts/ss/events/sc/083010mountsinabung', - "Click image to see photos from Indonesia's Mount Sinabung", - 'http://d.yimg.com/a/p/afp/20100831/capt.photo_1283212835701-1-0.jpg?x=400&y=266&q=85&sig=g2AZ_uDfPxzdnWIVohznYQ--', - '399', - '266', - 'AFP/Bay Ismoyo' -], -<!----------------------------------------------------------------------------[ UPDATED: SLOVAKIA SHOOTING ]--> -[ [['Another man shot and killed outside the building', 5]], - 'http://news.yahoo.com/nphotos/Deadly-Slovakia-Shooting/ss/events/wl/083010slovakiashoot', - "Click image to see photos from the Slovakia shooting", - 'http://d.yimg.com/a/p/rids/20100830/i/r2930448563.jpg?x=400&y=303&q=85&sig=HQPoUMpif9p7_fGVVA9ebA--', - '399', - '303', - 'Reuters//Lisi Niesner' -], -<!----------------------------------------------------------------------------[ UPDATED: CHINA TRAFFIC ]--> -[ [['A team of AFP reporters drove 260 kilometres,', 5]], - 'http://news.yahoo.com/nphotos/Huge-China-Traffic-Jam/ss/events/wl/082310chinatraffic', - "Click image to see photos of the huge traffic jam", - 'http://d.yimg.com/a/p/ap/20100824/capt.1f2a9423cbcf423aa86edbc87100a840-1f2a9423cbcf423aa86edbc87100a840-0.jpg?x=400&y=253&q=85&sig=k_znmWgDdxo5MvrT2FsKnw--', - '399', - '253', - 'AP' -], -<!----------------------------------------------------------------------------[ UPDATED: OVAL OFFICE ]--> -[ [['The White House declined to reveal the overall cost', 6], ['These sofas look like you could have a lot of long talks', 6]], - 'http://news.yahoo.com/nphotos/Oval-Office-Renovations/ss/events/pl/083110ovaloffice', - "Click image to see photos of the Oval Office renovations", - 'http://d.yimg.com/a/p/ap/20100831/capt.9941b0533bf140ebabbb1f311a5944b7-9941b0533bf140ebabbb1f311a5944b7-0.jpg?x=257&y=345&q=85&sig=AYnEnoAUDKqxy.AmpRuWnw--', - '257', - '344', - 'AP/J. Scott Applewhite' -], -<!----------------------------------------------------------------------------[ UPDATED: KATRINA HIGHWAY ]--> -[ [['From the beaches of Mississippi to the funky neighborhoods', 6]], - 'http://news.yahoo.com/nphotos/Mixed-recovery-along-hurricane-highway/ss/events/us/082510katrinahighway', - "Click image to see photos of recovery along Highway 90", - 'http://d.yimg.com/a/p/ap/20100825/capt.880129bb629b40d1acf403bf5885916b-880129bb629b40d1acf403bf5885916b-0.jpg?x=400&y=266&q=85&sig=HYATKBaHY8JaVKIJTMPbuA--', - '399', - '266', - 'AP/Gerald Herbert' -], -<!----------------------------------------------------------------------------[ UPDATED: GREECE ROYAL WEDDING ]--> -[ [['Prince Nikolaos', 5]], - 'http://news.yahoo.com/nphotos/Greece-Royal-Wedding/ss/events/wl/082510greeceroyal', - "Click image to see photos from the wedding", - 'http://d.yimg.com/a/p/ap/20100825/capt.7e28d880e341422bb3fa978d0be04a60-7e28d880e341422bb3fa978d0be04a60-0.jpg?x=400&y=292&q=85&sig=JZ5Ec4n6cNJEIuhDTDGwKw--', - '399', - '292', - 'AP/Dimitri Messinis' -], -<!----------------------------------------------------------------------------[ UPDATED: WILDEBEEST ]--> -[ [['migrations left on Earth', 5]], - 'http://news.yahoo.com/nphotos/Serengeti-Wildebeest-Migration/ss/events/sc/082510wildebeest', - "Click image to see photos of the wildebeest migration", - 'http://d.yimg.com/a/p/ap/20100825/capt.dcca89f701ea4167b18f8f1eb1488ff5-941b5a6f870c4d968d8c1dcafa89374a-0.jpg?x=400&y=265&q=85&sig=9v_dfkvnR5ySPFoepJbOeQ--', - '400', - '265', - 'AP/Sarah Durant, Wildlife Conservation Society' -], -<!----------------------------------------------------------------------------[ UPDATED: KERMIT ]--> -[ [['original Kermit the Frog', 6]], - 'http://news.yahoo.com/nphotos/Jim-Henson-early-puppets/ss/events/en/082510kermit', - "Click image to see photos of Jim Henson's early puppets", - 'http://d.yimg.com/a/p/ap/20100825/capt.de79e1d697f646a083117092161b6781-de79e1d697f646a083117092161b6781-0.jpg?x=400&y=266&q=85&sig=V4iIChL.QjhPlRBcCNVI8g--', - '399', - '266', - 'AP/Jacquelyn Martin' -], -<!----------------------------------------------------------------------------[ UPDATED: MOTHER TERESA ]--> -[ [['Mother Teresa', 5]], - 'http://news.yahoo.com/nphotos/Mother-Teresa-Remembered-100th-Birthday/ss/events/wl/082610motherteresa', - "Click on image to see photos of people remembering Mother Teresa", - 'http://d.yimg.com/a/p/ap/20100826/capt.54e7befe73b5405cb5004043c7861744-54e7befe73b5405cb5004043c7861744-0.jpg?x=400&y=249&q=85&sig=sxgdrmer5LutCZ8lEoNB5g--', - '399', - '249', - 'AP/Bikas Das' -], -<!----------------------------------------------------------------------------[ UPDATED: EMERALD ]--> -[ [['BBCarolina Emperor', 2]], - 'http://news.yahoo.com/nphotos/Carolina-Emperor-Emerald/ss/events/sc/083010carolinaemeral', - "Click image to see photos of the nearly 65-carat emerald", - 'http://d.yimg.com/a/p/net/20100830/capt.c88da0928dff2ce75f2a3d93450638e8.jpeg?x=400&y=299&q=85&sig=1b0UCep4vO0s9CqADaPGLg--', - '400', - '299', - 'AP/Courtesy of Terry Ledford' -], -<!---------------------------------------------------------------------------- UPDATED: TENN MOSQUE --> -[ [['Murfreesboro', 5]], - 'http://news.yahoo.com/nphotos/Tenn-Mosque-Debate/ss/events/us/083010tennmosque', - "Click on image to see photos of the Tenn. mosque debate", - 'http://d.yimg.com/a/p/ap/20100808/capt.62aedfa94fa44872bd33dce5bc7f91be-62aedfa94fa44872bd33dce5bc7f91be-0.jpg?x=400&y=267&q=85&sig=VZxsA7V_Cv14_4c55oX7BA--', - '399', - '267', - 'AP/Christopher Berkey' -], -<!----------------------------------------------------------------------------[ UPDATED: ZSA ZSA GABOR 8/31 ]--> -[ [['Zsa Zsa Gabor', 9]], - 'http://news.yahoo.com/nphotos/Zsa-Zsa-Gabor/ss/events/en/071910gabor', - "Click image to see more Zsa Zsa Gabor photos", - 'http://d.yimg.com/a/p/afp/20100831/capt.photo_1283282633228-1-0.jpg?x=253&y=345&q=85&sig=iRuo_0B8JuWA7EJkI9IXwQ--', - '253', - '345', - 'AFP/Kim Kulish' -], -<!----------------------------------------------------------------------------[ UPDATED: xx/xx ]--> -[ [['Four U.S. Chinook helicopters landed', 8]], - 'http://news.yahoo.com/nphotos/Pakistan-flooding-ravages-countryside-kills-hundreds/ss/events/wl/073010peshfloods', - "Click image to see photos of relief efforts in Pakistan", - 'http://d.yimg.com/a/p/ap/20100805/capt.51377930a8a04a3798e6659817a28015-51377930a8a04a3798e6659817a28015-0.jpg?x=400&y=266&q=85&sig=E9_xQwkXsEaKLTSuvEXh9Q--', - '400', - '266', - 'AP' -], -<!----------------------------------------------------------------------------[ UPDATED: xx/xx ]--> -[ [['to make good on his promise to end U.S. combat operations in Iraq by the end of August', 11]], - 'http://news.yahoo.com/nphotos/US-troops-withdrawing-Iraqi-cities/ss/events/wl/063009iraqtroopsout', - "Click image to see more photos of Iraq drawdown", - 'http://d.yimg.com/a/p/afp/20100705/capt.photo_1278267588938-1-0.jpg?x=400&y=274&q=85&sig=rD75zKa8bQGbUmOTE1wi1w--', - '400', - '274', - 'AFP' -], -<!----------------------------------------------------------------------------[ UPDATED: xx/xx ]--> -[ [['Kagan then recited a second oath, taken by judges, with her family and friends and reporters present', 9]], - 'http://news.yahoo.com/nphotos/Supreme-Court-Justice-Elena-Kagan/ss/events/pl/041610elenakagan', - "Click image to see more photos of Elena Kagan's confirmation", - 'http://d.yimg.com/a/p/ap/20100807/capt.b236658ec08c461faf972ebbbbacf608-b236658ec08c461faf972ebbbbacf608-0.jpg?x=325&y=345&q=85&sig=Z1Uo2viJ1L3vdYKflEjY0A--', - '325', - '344', - 'AP' -], -<!----------------------------------------------------------------------------[ UPDATED: FRANCE GYPIES ]--> -[ [['crackdown on Gypsies', 5]], - 'http://news.yahoo.com/nphotos/France-send-Gypsies-back-Romania/ss/events/wl/081910francegypsies', - "Click image to see photos of the crackdown", - 'http://d.yimg.com/a/p/afp/20100827/capt.photo_1282937836505-5-0.jpg?x=400&y=265&q=85&sig=vktmLFsDwqw4akqZoUvg2g--', - '400', - '265', - 'AFP/Philippe Huguen' -], -<!----------------------------------------------------------------------------[ UPDATED: xx/xx ]--> -[ [['BA document written by a federal judge 216 years ago', 5]], - 'http://news.yahoo.com/nphotos/1794-document-found-Eisenhower-archives/ss/events/lf/081910eisenhowerpape', - "Click image to see photos from the Eisenhower archives", - 'http://d.yimg.com/a/p/ap/20100819/capt.e5f9778858a045408243118bf55fcf58-e5f9778858a045408243118bf55fcf58-0.jpg?x=400&y=276&q=85&sig=ZrooGj_Rfgk24TD0yJ4ODg--', - '400', - '276', - 'AP/John Milburn' -] - - - - - - - -]; - - -<!-- Currently used in sections: sc, pl, wl, us, en, bs, ts --> - - - - -<!---------------------------- DO NOT EDIT BEYOND THIS POINT -------------------------------------> - - story_content = $("div.yn-story-content").html(); - - for (var p in photos) { - for (var snippet in photos[p][0]) { - if (story_content.indexOf(photos[p][0][snippet][0]) >= 0) { - slideshow_link = photos[p][1]; - slideshow_link_text = photos[p][2]; - embedded_img = photos[p][3]; - img_width = photos[p][4]; - img_height = photos[p][5]; - img_credit = photos[p][6]; - embed_after_paragraph_number = photos[p][0][snippet][1]; - slideshow_code = '<div align="center"><p><a href="'+slideshow_link+';_ylt=A2KIKvTWiH1Ml3sBRwNu.aF4;_ylu=X3oDMTE3OW44Nm4xBHBvcwMyMwRzZWMDeW5fZmVhdHVyZWQEc2xrA3NsaWRlc2hvd19saQ--" ><b>' + slideshow_link_text + '</b></a></p><p style="width: ' + img_width + 'px; text-align: right;"><a href="'+slideshow_link+';_ylt=A2KIKvTWiH1Ml3sBSANu.aF4;_ylu=X3oDMTEwaWZ1YWs0BHBvcwMyNARzZWMDeW5fZmVhdHVyZWQEc2xrA2ltYWdl" ><img src="' + embedded_img + '" width="' + img_width + '" height="' + img_height + '" /></a><br /><cite id="captionCite">' + img_credit + '</cite></p></div>'; - - - $("div.yn-story-content p:eq(" + embed_after_paragraph_number + ")").after(slideshow_code); - break; - } - } - } -}); -</script></li><li><!-- START PROMO --> - -<script type="text/javascript"> -if(typeof jQuery==='undefined') - document.write(unescape('%3Cscript type="text/javascript" src="http://l.yimg.com/d/lib/news/p/common/generic/jquery_1.4.2-min-42268.js"%3E%3C/script%3E')); -</script> - -<style type="text/css"> -#floatLeft { float:left; width:350px; margin:3px 15px 7px 0px; } -#border3sides { border:1px solid #ddd; border-top:0px; } -.bluebar { background-image:url('http://l.yimg.com/a/p/us/news/editorial/5/23/523913efe3993cc8c2ccf7ccc683d968.jpeg'); background-repeat:no-repeat; padding:5px; font:normal 20px arial; } -.bluebarlink { color:white; text-decoration:none; } -.theThumb { width: 75px; height:55px; border:0; } -#text { width:330px; height:20px; padding-top:5px; } -#smThumb p { float:left; padding:10px 9px 0 0; font:11px arial; width: 75px; } -#smThumb a { color:#666; text-decoration:none; } -.thumbText { font:14px arial; text-decoration:none; color:#000; } -</style> - -<script type="text/javascript"> - -var thumbText1 = '<a href="http://news.yahoo.com/s/ynews/ynews_ts3464;_ylt=A2KIKvTWiH1Ml3sBSQNu.aF4;_ylu=X3oDMTE3NG0wYjJzBHBvcwMyNQRzZWMDeW5fZmVhdHVyZWQEc2xrA25ld29ybGVhbnNmcg--" class="thumbText">New Orleans\' fragile recovery, 5 years after Katrina</a>'; -var thumbText2 = '<a href="http://news.yahoo.com/s/ynews_spec/ynews_spec_ts3448;_ylt=A2KIKvTWiH1Ml3sBSgNu.aF4;_ylu=X3oDMTE3cmhhaXJvBHBvcwMyNgRzZWMDeW5fZmVhdHVyZWQEc2xrA3Bob3Rvc2FuYXRvbQ--" class="thumbText">Photos: Anatomy of a disaster</a>'; -var thumbText3 = '<a href="http://news.yahoo.com/nphotos/Then-and-now-New-Orleans-5-Years-after-Katrina/ss/events/us/082410katrinathennow;_ylt=A2KIKvTWiH1Ml3sBSwNu.aF4;_ylu=X3oDMTE3MDljbjJyBHBvcwMyNwRzZWMDeW5fZmVhdHVyZWQEc2xrA3Bob3Rvc25ld29ybA--" class="thumbText">Photos: New Orleans, then and now</a>'; -var thumbText4 = '<a href="http://news.yahoo.com/s/ac/20100823/tr_ac/6628782_cleaning_up_after_hurricane_katrina_uncovers_lifes_lessons;_ylt=A2KIKvTWiH1Ml3sBTANu.aF4;_ylu=X3oDMTE3cGRjYzBoBHBvcwMyOARzZWMDeW5fZmVhdHVyZWQEc2xrA2NsZWFuaW5ndXBhZg--" class="thumbText">Cleaning up after Katrina uncovers life lessons</a>'; - - -function onover(what){ -document.getElementById('text').innerHTML=''+what+''; - -rx = new RegExp("href=\"([^\"]*)\""); -link = what.match(rx)[1]; -$("#ChristinesLink").attr("href", link); - -} -function onout(){ -document.getElementById('text').innerHTML=''; -} -</script> - - - -<script type="text/javascript"> - - if (document.images) { -image0 = new Image; -image1 = new Image; -image2 = new Image; -image3 = new Image; - -image0.src = 'http://l.yimg.com/a/p/us/news/editorial/1/cf/1cf64fdceffce63f2d1794936c2ed81c.jpeg'; -image1.src = 'http://l.yimg.com/a/p/us/news/editorial/b/99/b99244314f5bb86fe97ff3d9e72eb180.jpeg'; -image2.src = 'http://l.yimg.com/a/p/us/news/editorial/5/29/52989b60cef5569da3448e6886aed4b7.jpeg'; -image3.src = 'http://d.yimg.com/a/p/rids/20100817/i/r3994317792.jpg?x=400&y=266&q=85&sig=U248J4rTGZzouhs56N_ZOA--'; - - -} else { -image0 = ''; -image1 = ''; -image2 = ''; -image3 = ''; -document.rollimg = ''; -} - -</script> - - - - - - -<script type="text/javascript"> -$(document).ready(function() { -$("#yn-featured li:visible:last").css("display","none"); - - - var embed_thingie = '<div id="floatLeft"><div class="bluebar"><a href="http://news.yahoo.com/s/ynews/ynews_ts3464;_ylt=A2KIKvTWiH1Ml3sBTQNu.aF4;_ylu=X3oDMTE3dHQ4cWc2BHBvcwMyOQRzZWMDeW5fZmVhdHVyZWQEc2xrAzV5ZWFyc2FmdGVyaw--" class="bluebarlink">5 years after Katrina &raquo;</a></div><div id="border3sides" style="padding-left:10px;"><div style="padding-top:10px;"><img src="http://l.yimg.com/a/p/us/news/editorial/1/cf/1cf64fdceffce63f2d1794936c2ed81c.jpeg" width="327" height="180" border="0" alt="Larger version" name="rollimg" /><span id="text" class="thumbText">New Orleans\' fragile recovery, 5 years after Katrina</span></div><div id="smThumb"><p><a href="http://news.yahoo.com/s/ynews/ynews_ts3464;_ylt=A2KIKvTWiH1Ml3sBTgNu.aF4;_ylu=X3oDMTE3c2RsYmZqBHBvcwMzMARzZWMDeW5fZmVhdHVyZWQEc2xrA2ZyYWdpbGVzdGF0ZQ--" onmouseover="onover(thumbText1)"><span onmouseover="document.rollimg.src=image0.src;"><img src="http://l.yimg.com/a/p/us/news/editorial/1/cf/1cf64fdceffce63f2d1794936c2ed81c.jpeg" class="theThumb" /><br />Fragile state</span></a></p><p><a href="http://news.yahoo.com/s/ynews_spec/ynews_spec_ts3448;_ylt=A2KIKvTWiH1Ml3sBTwNu.aF4;_ylu=X3oDMTE3ZnMxZmhuBHBvcwMzMQRzZWMDeW5fZmVhdHVyZWQEc2xrA2Rpc2FzdGVyaGl0cw--" onmouseover="onover(thumbText2)"><span onmouseover="document.rollimg.src=image1.src;"><img src="http://l.yimg.com/a/p/us/news/editorial/b/99/b99244314f5bb86fe97ff3d9e72eb180.jpeg" class="theThumb" /><br />Disaster hits</span></a></p><p><a href="http://news.yahoo.com/nphotos/Then-and-now-New-Orleans-5-Years-after-Katrina/ss/events/us/082410katrinathennow;_ylt=A2KIKvTWiH1Ml3sBUANu.aF4;_ylu=X3oDMTE1ZDE1dTVlBHBvcwMzMgRzZWMDeW5fZmVhdHVyZWQEc2xrA3RoZW5hbmRub3c-" onmouseover="onover(thumbText3)"><span onmouseover="document.rollimg.src=image2.src;"><img src="http://l.yimg.com/a/p/us/news/editorial/5/29/52989b60cef5569da3448e6886aed4b7.jpeg" class="theThumb" /><br />Then and now</span></a></p><p style="padding-right:0 !important;"><a href="http://news.yahoo.com/s/ac/20100823/tr_ac/6628782_cleaning_up_after_hurricane_katrina_uncovers_lifes_lessons;_ylt=A2KIKvTWiH1Ml3sBUQNu.aF4;_ylu=X3oDMTE2cTA4NHNlBHBvcwMzMwRzZWMDeW5fZmVhdHVyZWQEc2xrA2xpZmVsZXNzb25z" onmouseover="onover(thumbText4)"><span onmouseover="document.rollimg.src=image3.src;"><img src="http://d.yimg.com/a/p/rids/20100817/i/r3994317792.jpg?x=400&y=266&q=85&sig=U248J4rTGZzouhs56N_ZOA--" class="theThumb" /><br />Life lessons</span></a></p></div><div style="clear:both;"></div></div></div>'; - - story_content = $("div.yn-story-content").html(); - - if (story_content.indexOf("Hurricane Katrina") >= 0) - $("div.yn-story-content p:eq(7)").after(embed_thingie); - - -}); -</script></li> </ul> - </div> -</div> - -<div id="promo-education" class="mod promo"> - <div class="hd"> - <h3>Education<span>Yahoo</span></h3> - </div> - <div class="bd"> - <div class="headlines"> - <div class="hd"></div> - <div class="bd"> - <ul> - <li class="first"><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBUgNu.aF4;_ylu=X3oDMTFlNmU3c2F2BHBvcwMxBHNlYwN5bl9wcm9tb3NfZWR1Y2F0aW9uBHNsawN5YWhvb2VkdWNhdGk-/SIG=12c627dut/**http%3A//education.yahoo.net/articles/move_into_management.htm%3Fkid=SR3Y" ><img width="54" height="54" alt="Yahoo! Education" src="http://l.yimg.com/a/p/us/news/editorial/4/69/469d217a8aacdd62afc2b9c1067ab18d.jpeg"/></a> -<div><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBUwNu.aF4;_ylu=X3oDMTFlamFibDFmBHBvcwMyBHNlYwN5bl9wcm9tb3NfZWR1Y2F0aW9uBHNsawNhcmV5b3VhbGVhZGU-/SIG=12c627dut/**http%3A//education.yahoo.net/articles/move_into_management.htm%3Fkid=SR3Y" >Are You A Leader?</a>See how the right training and education can prepare you for management!</div></li><li><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBVANu.aF4;_ylu=X3oDMTFlbGlxczJlBHBvcwMzBHNlYwN5bl9wcm9tb3NfZWR1Y2F0aW9uBHNsawN5YWhvb2VkdWNhdGk-/SIG=12d48ks0b/**http%3A//education.yahoo.net/articles/online_mba_fast_facts.htm%3Fkid=SRBR" ><img width="54" height="54" alt="Yahoo! Education" src="http://l.yimg.com/a/p/us/news/editorial/5/65/565a10b133247386df8def7b4819c14a.jpeg"/></a> -<div><a href="http://us.lrd.yahoo.com/_ylt=A2KIKvTWiH1Ml3sBVQNu.aF4;_ylu=X3oDMTFldjJscTV1BHBvcwM0BHNlYwN5bl9wcm9tb3NfZWR1Y2F0aW9uBHNsawNmYXN0ZmFjdHNvbmw-/SIG=12d48ks0b/**http%3A//education.yahoo.net/articles/online_mba_fast_facts.htm%3Fkid=SRBR" >Fast Facts: Online MBAs</a>Join us as we separate online MBA facts from fiction.</div></li> </ul> - </div> - <div class="ft"></div> - </div> - </div> -</div> - -<div id="promo-finance" class="mod promo"> - <div class="hd"> - <h3><span>Yahoo!</span><a href="http://finance.yahoo.com/">Finance</a></h3> - </div> - <div class="bd"> - <div class="search"> - <form action="http://finance.yahoo.com/q" method="get"> - <input class="search-text" type="text" name="s"> - <input class="search-submit" type="submit" value="Get Quotes"> - </form> - </div> - <div class="related-quotes"> - <em>Market Summary</em> - <table cellspacing="0"> - <thead> - <tr> - <th>Symbol</th> - <td>Last</td> - <td colspan="2">Change</td> - </tr> - </thead> - <tbody> - <tr> - <th><a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBVgNu.aF4;_ylu=X3oDMTFhb21tbWY2BHBvcwMxBHNlYwN5bl9wcm9tb3NfZmluYW5jZV9xdW90ZXMEc2xrA2Rvdw--?s=^DJI" >Dow</a></th> - <td>10,014.72</td> - <td class="positive">+4.99</td> - <td class="positive">+0.05%</td> - </tr> -<tr class="alternate"> - <th><a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBVwNu.aF4;_ylu=X3oDMTFkcHRkcmU4BHBvcwMyBHNlYwN5bl9wcm9tb3NfZmluYW5jZV9xdW90ZXMEc2xrA25hc2RhcQ--?s=^IXIC" >Nasdaq</a></th> - <td>2,114.03</td> - <td class="negative">-5.94</td> - <td class="negative">-0.28%</td> - </tr> -<tr> - <th><a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBWANu.aF4;_ylu=X3oDMTFjMzBjcXFxBHBvcwMzBHNlYwN5bl9wcm9tb3NfZmluYW5jZV9xdW90ZXMEc2xrA3NwNTAw?s=^GSPC" >S&P 500</a></th> - <td>1,049.33</td> - <td class="positive">+0.41</td> - <td class="positive">+0.04%</td> - </tr> -<tr class="alternate"> - <th><a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBWQNu.aF4;_ylu=X3oDMTFmbTByZjQwBHBvcwM0BHNlYwN5bl9wcm9tb3NfZmluYW5jZV9xdW90ZXMEc2xrAzEweXJib25k?s=^TNX" >10 Yr Bond(%)</a></th> - <td>2.4770%</td> - <td colspan="2" class="negative">-0.6800</td> - </tr> -<tr> - <th><a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBWgNu.aF4;_ylu=X3oDMTFhMWlyMGR0BHBvcwM1BHNlYwN5bl9wcm9tb3NfZmluYW5jZV9xdW90ZXMEc2xrA29pbA--?s=CLV10.NYM" >Oil</a></th> - <td>71.74</td> - <td class="negative">-0.18</td> - <td class="negative">-0.25%</td> - </tr> -<tr class="alternate"> - <th><a href="http://finance.yahoo.com/q;_ylt=A2KIKvTWiH1Ml3sBWwNu.aF4;_ylu=X3oDMTFiODVpOGttBHBvcwM2BHNlYwN5bl9wcm9tb3NfZmluYW5jZV9xdW90ZXMEc2xrA2dvbGQ-?s=GCU10.CMX" >Gold</a></th> - <td>1,248.30</td> - <td>0.00</td> - <td>0.00%</td> - </tr> - - </tbody> - </table> - </div> - <ul> - <li class="first"><a href="http://us.rd.yahoo.com/finance/news/topnews;_ylt=A2KIKvTWiH1Ml3sBXANu.aF4;_ylu=X3oDMTFqZmNlcGMxBHBvcwMxBHNlYwN5bl9wcm9tb3NfZmluYW5jZV9oZWFkbGkEc2xrA2FtYXpvbnRyeWluZw--/*http://biz.yahoo.com/ap/100831/us_amazon_subscription_video.html" >Amazon trying to offer subscription TV, movies</a></li> -<li><a href="http://us.rd.yahoo.com/finance/news/topnews;_ylt=A2KIKvTWiH1Ml3sBXQNu.aF4;_ylu=X3oDMTFqNWk0ZGFrBHBvcwMyBHNlYwN5bl9wcm9tb3NfZmluYW5jZV9oZWFkbGkEc2xrA2ZlZGFwcHJvdmVzcw--/*http://biz.yahoo.com/ap/100831/us_fed_china_morgan_stanley.html" >Fed approves sale of Morgan Stanley stock shares</a></li> -<li><a href="http://us.rd.yahoo.com/finance/news/topnews;_ylt=A2KIKvTWiH1Ml3sBXgNu.aF4;_ylu=X3oDMTFqY2s5cjgwBHBvcwMzBHNlYwN5bl9wcm9tb3NfZmluYW5jZV9oZWFkbGkEc2xrA3N0b2Nrc2VuZGFicg--/*http://biz.yahoo.com/ap/100831/us_wall_street.html" >Stocks end a brutal August with meager gains</a></li> - - </ul> - </div> - <div class="ft"><a href="http://finance.yahoo.com/">More from Yahoo! Finance &raquo;</a></div> -</div> -<div id="darla-ad__LREC2" class="mod ad darla_ad mod-first"></div> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - </div><!-- end: #sidebar --> - - </div><!-- end: #bd --> - - <div id="ft"> - - <div id="yn_ft_nav" class="ult-section nav"> - <h6>Yahoo! News Navigation</h6> - <ul> - <li class="ult-position first"><a href="/;_ylt=A2KIKvTWiH1Ml3sBcwNu.aF4;_ylu=X3oDMTBscGJtNnJwBHBvcwMxBHNlYwNibgRzbGsDaG9tZQ--" >Home</a></li> - <li class="ult-position"><a href="/us;_ylt=A2KIKvTWiH1Ml3sBdANu.aF4;_ylu=X3oDMTBqYWlyY3JoBHBvcwMyBHNlYwNibgRzbGsDdXM-" ><abbr title="United States">U.S.</abbr></a></li> - <li class="ult-position"><a href="/business;_ylt=A2KIKvTWiH1Ml3sBdQNu.aF4;_ylu=X3oDMTBwZDFmMzRuBHBvcwMzBHNlYwNibgRzbGsDYnVzaW5lc3M-" >Business</a></li> - <li class="ult-position"><a href="/world;_ylt=A2KIKvTWiH1Ml3sBdgNu.aF4;_ylu=X3oDMTBtZmVxZWZuBHBvcwM0BHNlYwNibgRzbGsDd29ybGQ-" >World</a></li> - <li class="ult-position"><a href="/entertainment;_ylt=A2KIKvTWiH1Ml3sBdwNu.aF4;_ylu=X3oDMTB0aWVuaTE3BHBvcwM1BHNlYwNibgRzbGsDZW50ZXJ0YWlubWVu" >Entertainment</a></li> - <li class="ult-position"><a href="/sports;_ylt=A2KIKvTWiH1Ml3sBeANu.aF4;_ylu=X3oDMTBuOW00dmhtBHBvcwM2BHNlYwNibgRzbGsDc3BvcnRz" >Sports</a></li> - <li class="ult-position"><a href="/technology;_ylt=A2KIKvTWiH1Ml3sBeQNu.aF4;_ylu=X3oDMTBsMjJpYzZsBHBvcwM3BHNlYwNibgRzbGsDdGVjaA--" >Tech</a></li> - <li class="ult-position"><a href="/politics;_ylt=A2KIKvTWiH1Ml3sBegNu.aF4;_ylu=X3oDMTBwMXRyZ2tyBHBvcwM4BHNlYwNibgRzbGsDcG9saXRpY3M-" >Politics</a></li> - <li class="ult-position"><a href="/science;_ylt=A2KIKvTWiH1Ml3sBewNu.aF4;_ylu=X3oDMTBvczFoaG5qBHBvcwM5BHNlYwNibgRzbGsDc2NpZW5jZQ--" >Science</a></li> - <li class="ult-position"><a href="/health;_ylt=A2KIKvTWiH1Ml3sBfANu.aF4;_ylu=X3oDMTBvMjE3NmRyBHBvcwMxMARzZWMDYm4Ec2xrA2hlYWx0aA--" >Health</a></li> - <li class="ult-position"><a href="/travel;_ylt=A2KIKvTWiH1Ml3sBfQNu.aF4;_ylu=X3oDMTBvcThkNjZqBHBvcwMxMQRzZWMDYm4Ec2xrA3RyYXZlbA--" >Travel</a></li> - <li class="ult-position"><a href="/most-popular;_ylt=A2KIKvTWiH1Ml3sBfgNu.aF4;_ylu=X3oDMTB0dmxhYjRuBHBvcwMxMgRzZWMDYm4Ec2xrA21vc3Rwb3B1bGFy" >Most Popular</a></li> - <li class="ult-position"><a href="/odd;_ylt=A2KIKvTWiH1Ml3sBfwNu.aF4;_ylu=X3oDMTBwNjdvb3M2BHBvcwMxMwRzZWMDYm4Ec2xrA29kZG5ld3M-" >Odd News</a></li> - <li class="ult-position"><a href="/opinion;_ylt=A2KIKvTWiH1Ml3sBgANu.aF4;_ylu=X3oDMTBwYXNubW5pBHBvcwMxNARzZWMDYm4Ec2xrA29waW5pb24-" >Opinion</a></li> - </ul> - </div><!-- end: .nav --> - - <div id="yn_ft_services" class="ult-section services"> - <h6>Yahoo! News Network</h6> - <ul> - <li class="ult-position rss first"><a href="/rss;_ylt=A2KIKvTWiH1Ml3sBgQNu.aF4;_ylu=X3oDMTBsa3NwcDZrBHBvcwMxNQRzZWMDYm4Ec2xrA3Jzcw--" ><abbr title="Really Simple Syndication">RSS</abbr></a></li> - <li class="ult-position"><a href="http://us.lrd.yahoo.com/SIG=1335fllhs;_ylt=A2KIKvTWiH1Ml3sBggNu.aF4;_ylu=X3oDMTBzM2g2NnBkBHBvcwMxNgRzZWMDYm4Ec2xrA25ld3NhbGVydHM-/**http%3A//beta.alerts.yahoo.com/main.php%3Fview=create_news_step1%26.done=http%3A//news.yahoo.com" >News Alerts</a></li> - <li class="ult-position"><a href="http://us.lrd.yahoo.com/SIG=120f8b0qi;_ylt=A2KIKvTWiH1Ml3sBgwNu.aF4;_ylu=X3oDMTB1OTk2OTN2BHBvcwMxNwRzZWMDYm4Ec2xrA3dlYXRoZXJhbGVydA--/**http%3A//beta.alerts.yahoo.com/main.php%3Fview=create_weather" >Weather Alerts</a></li> - <li class="ult-position"><a href="/page/sitemap;_ylt=A2KIKvTWiH1Ml3sBhANu.aF4;_ylu=X3oDMTBwdXVvZjhxBHBvcwMxOARzZWMDYm4Ec2xrA3NpdGVtYXA-" >Site Map</a></li> - <li class="ult-position"><a href="http://us.lrd.yahoo.com/SIG=11bqi29dl;_ylt=A2KIKvTWiH1Ml3sBhQNu.aF4;_ylu=X3oDMTBtNzdqa2hhBHBvcwMxOQRzZWMDYm4Ec2xrA2hlbHA-/**http%3A//help.yahoo.com/l/us/yahoo/news/" >Help</a></li> - <li class="ult-position"><a href="http://us.lrd.yahoo.com/SIG=11ehrsfi3;_ylt=A2KIKvTWiH1Ml3sBhgNu.aF4;_ylu=X3oDMTBxYThzODBpBHBvcwMyMARzZWMDYm4Ec2xrA2ZlZWRiYWNr/**http%3A//suggestions.yahoo.com/%3Fprop=news" >Feedback</a></li> - </ul> - - </div><!-- end: .services --> - -</div><!-- end: #ft --> - - - - <div id="copyright" class="mod" role="contentinfo"> - <p>Copyright &copy; 2010 Reuters Limited. All rights reserved. Republication or redistribution of Reuters content is expressly prohibited without the prior written consent of Reuters. Reuters shall not be liable for any errors or delays in the content, or for any actions taken in reliance thereon. -<br></p> -</div> - - - <div class="mod ad ad_footer" role="contentinfo"> - <div class="bd"> - <!-- Ad Keywords: it; business; price; yuan; government; Yuan;--> -<div id="copyright"><cite>Copyright &copy; 2010 Yahoo! Inc. All rights reserved.</cite><ul><li class="first"><a href="http://us.ard.yahoo.com/SIG=15r1t41b5/M=289534.13891068.13879306.12123427/D=news/S=2023881070:FOOT2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=6WFLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5304694/R=0/SIG=1184ggc4j;_ylt=A2KIKvTWiH1Ml3sBjANu.aF4;_ylu=X3oDMTExZWdkNmg4BHBvcwMxBHNlYwNmb290ZXIEc2xrA3F1ZXN0aW9uc29yYw--/*http://help.yahoo.com/l/us/yahoo/news/" >Questions or Comments</a></li><li><a href="http://us.ard.yahoo.com/SIG=15r1t41b5/M=289534.13891068.13879306.12123427/D=news/S=2023881070:FOOT2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=6WFLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5304694/R=1/SIG=11q1ej28f;_ylt=A2KIKvTWiH1Ml3sBjQNu.aF4;_ylu=X3oDMTExa2s2dXRsBHBvcwMyBHNlYwNmb290ZXIEc2xrA3ByaXZhY3lwb2xpYw--/*http://info.yahoo.com/privacy/us/yahoo/news/details.html" >Privacy Policy</a></li><li><a href="http://us.ard.yahoo.com/SIG=15r1t41b5/M=289534.13891068.13879306.12123427/D=news/S=2023881070:FOOT2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=6WFLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5304694/R=2/SIG=11475d5s1;_ylt=A2KIKvTWiH1Ml3sBjgNu.aF4;_ylu=X3oDMTEwODVuYzhrBHBvcwMzBHNlYwNmb290ZXIEc2xrA2Fib3V0b3VyYWRz/*http://info.yahoo.com/relevantads/" >About Our Ads</a></li><li><a href="http://us.ard.yahoo.com/SIG=15r1t41b5/M=289534.13891068.13879306.12123427/D=news/S=2023881070:FOOT2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=6WFLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5304694/R=3/SIG=1136qnvkg;_ylt=A2KIKvTWiH1Ml3sBjwNu.aF4;_ylu=X3oDMTExbWNza2cxBHBvcwM0BHNlYwNmb290ZXIEc2xrA3Rlcm1zb2ZzZXJ2aQ--/*http://docs.yahoo.com/info/terms/" >Terms of Service</a></li><li><a href="http://us.ard.yahoo.com/SIG=15r1t41b5/M=289534.13891068.13879306.12123427/D=news/S=2023881070:FOOT2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=6WFLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5304694/R=4/SIG=11lp7krrc;_ylt=A2KIKvTWiH1Ml3sBkANu.aF4;_ylu=X3oDMTExaTZqczBqBHBvcwM1BHNlYwNmb290ZXIEc2xrA2NvcHlyaWdodGlwcA--/*http://docs.yahoo.com/info/copyright/copyright.html" >Copyright/IP Policy</a></li></ul></div> -<!-- http://us.ard.yahoo.com/SIG=15r1t41b5/M=289534.13891068.13879306.12123427/D=news/S=2023881070:FOOT2/Y=YAHOO/EXP=1283302646/L=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_/B=6WFLBkoGYmQ-/J=1283295446553092/K=U7.lJSeRx2ofwgoGAUPgCQ/A=5304694/R=5/* --> -<!-- SpaceID=2023881070 loc=FR01 noad --> - -<!-- SpaceID=2023881070 loc=SIP noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['CGJLBkoGYmQ-']='&U=12b0if0c5%2fN%3dCGJLBkoGYmQ-%2fC%3d-1%2fD%3dSIP%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1avrl5dgt%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d2667847401%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12b0if0c5%2fN%3dCGJLBkoGYmQ-%2fC%3d-1%2fD%3dSIP%2fB%3d-1%2fV%3d0"></noscript><script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['6WFLBkoGYmQ-']='&U=13hquttf0%2fN%3d6WFLBkoGYmQ-%2fC%3d289534.13891068.13879306.12123427%2fD%3dFOOT2%2fB%3d5304694%2fV%3d1'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1av5ub2nm%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d4080607869%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=13hquttf0%2fN%3d6WFLBkoGYmQ-%2fC%3d289534.13891068.13879306.12123427%2fD%3dFOOT2%2fB%3d5304694%2fV%3d1"></noscript> </div> -</div> - - - </div><!-- end: #doc4 --> - - -<script type="text/javascript" src="http://l.yimg.com/d/combo?yui/2.7.0/build/yahoo/yahoo-min.js&yui/2.7.0/build/event/event-min.js&yui/2.7.0/build/dom/dom-min.js&yui/2.7.0/build/get/get-min.js&yui/2.7.0/build/animation/animation-min.js&yui/2.7.0/build/json/json-min.js&yui/2.7.0/build/connection/connection-min.js&yui/2.7.0/build/datasource/datasource-min.js&yui/2.7.0/build/selector/selector-min.js&yui/2.7.0/build/cookie/cookie-min.js&yui/2.7.0/build/container/container-min.js&uh/15/js/uh_rsa-1.0.9.js&news/p/common/generic/common_base_rollup-min-29176.js&news/p/common/generic/common_page_rollup-min-26267.js&media/m/location_widget/location_widget-min-22834.js&news/p/common/generic/filter-reload-local-viewer-filter_init-reload_init-min-38036.js&s5/miniassist_200912081429.js&news/p/story/generic/mediabuzz-story-min-39391.js&news/p/common/generic/foundation/im-min-6761.js&news/p/common/generic/foundation/popup-min-12622.js&news/p/common/generic/im_init-min-12623.js&news/p/common/generic/popup_init-min-12623.js&news/p/common/generic/yui3-min-36486.js&yui/2.7.0/build/imageloader/imageloader-min.js&yui/2.7.0/build/container/container_core-min.js&yui/2.7.0/build/element/element-min.js&media/phugc/mwphcom_min_r1413.js"></script> - - <script type="text/javascript"> - (function () { - var sa; - if (YAHOO.Search && YAHOO.Search.MiniAssist) { - sa = new YAHOO.Search.MiniAssist('yn-search-assist'); - sa.gossip.setConfigValue('pubid', 104); - } - })(); - </script> - - - <!-- Kontera ContentLink(TM);--> - <script type="text/javascript"> - var dc_PublisherID = '139449'; // use string to avoid syntax error - var dc_ChannelID = '749'; - var dc_ArticleDate = ':nm:20100831:bs_nm:us_gm_china_1'; - var HTTP_KONA = 'http://l.yimg.com/d/lib/news/projects/story/kontera/20100726'; - var HTTP_KONAC = HTTP_KONA; - var KONA_BEACON_URL = 'http://geo.yahoo.com/b?s=2143083931&amp;t=%t%'; - - //parse string back to number - dc_PublisherID = parseInt(dc_PublisherID); - dc_ChannelID = parseInt(dc_ChannelID); - </script> - <script type="text/javascript" src="http://l.yimg.com/d/lib/news/projects/story/kontera/20100726/javascript/lib/konterayahoooo.js"></script> - <!-- Kontera ContentLink(TM) --> - - - <script type="text/javascript"> - (function() { - // Set spaceid for page - YAHOO.News.SPACEID = '2023881070'; - // Timestamp - YAHOO.News.TIMESTAMP = '1283295446'; - // User signed in - YAHOO.News.USER_SIGNED_IN = '0'; - // User Crumb Recommend - YAHOO.News.USER_CRUMB_RECOMMEND = 'g/J7qV7knFx'; - // Set page type - YAHOO.News.PAGE_TYPE = 'story'; - // Pageview page 2 for expand/collapse roundtrip and comscore - YAHOO.News.PAGEVIEW_PAGE2 = '/s/nm/20100831/bs_nm/us_gm_china/page2'; - // Pageview page 2 comscore tracking (use env var to set this flag) - YAHOO.News.PAGEVIEW_PAGE2_TRACKING = '0'; - // Track pageview for story and most viewed data - YAHOO.News.PAGEVIEW_MOST_VIEWED = new YAHOO.News.Pageview('/news/common/pages/generic/pageview/nm:20100831:bs_nm:us_gm_china/749/'); - // Darla URL - YAHOO.News.DARLA_URL = '/news/common/pages/generic/darla/fc'; - // Darla Keywords - YAHOO.News.DARLA_KEYWORDS = 'it; business; price; yuan; government; Yuan;'; - // Referrer - YAHOO.News.DARLA_REFERRER = ''; - // Domain value for cookies - YAHOO.News.COOKIE_DOMAIN = '.news.yahoo.com'; - // if in single-url mode (url is news.yahoo.com) then call hidden iframe for pageview - if (window.location.href == "http://news.yahoo.com/") { - // Pageview beacon url for comscore - YAHOO.News.PAGEVIEW_BEACON = '/s/b/nm/20100831/bs_nm/us_gm_china/'; - // Hidden iframe - YAHOO.News.PAGEVIEW_IFRAME = new YAHOO.News.Pageview(YAHOO.News.PAGEVIEW_BEACON); - } - })(); - </script> - - -<script type="text/javascript" src="http://l.yimg.com/d/combo?media/m/social_buttons/social-buttons-easy-min-3602.js"></script> - <script language="javascript"> - - if(!("Media" in YAHOO)){YAHOO.Media = {};} - if(!("MwPhCom" in YAHOO.Media)){YAHOO.Media.MwPhCom = {};} - YAHOO.Media.MwPhCom.coms = new YAHOO.phugcmwpcmt.comments({ - asset_id:"nmus_gm_china", - asset_cat:"headcontent", - login_url:"http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china", - section:"main", - sServiceUrl:"/news/common/maple/en-US", - sAppLang:"en-US", - sAbuseUrl:"http://help.yahoo.com/l/us/yahoo/news/forms/abuse_articles.html", - sCharCountAllowed:"4000", - rating_type:"up_down", - rating_url:"mwphucmtrate", - sParentAssetUrl:"http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china", - sParentAssetTitle:"Dummy", - sVitalityProperty:"y.news", - sVitalityRating:"0", - sVitalityRatingType:"0", - sVitalityPosting:"1", - sAllowReply:"1", - sDisclosureProp:"y.news", - sDisclosureRateEvent:"genericrate", - sVitalityConsumption:"0", - sUltBeaconSec:"na", - sPostingDisabled:"N", - sUltBeaconSpID:"2142428251", - sUltBeaconEnable:"1", - sUtlBeaconRcdPgVew:"0", - sUltBeaconProp:"news", - sSortOptionDisp:"Newest First", - sYuiBasePath:"yui/2.7.0/build/", - sCmtGuideLineUrl:"http://help.yahoo.com/l/us/yahoo/news/comments/comment-guidelines.html", - sPostCrumb:"dJlYyrqFpdz", - sSortBy:"date", - sSortOrder:"desc", - sStartForm:"1", - sFilterBy:"", - sAssetExcerpt:"VGhpcyBpcyBleGNlcnB0IiBvZiBhcnRpY2xlIA==", - sAssetImgURL:"", - sAssetBlogName:"", - sAssetImgSige:"", - sAssetAuthor:"", - sAssetTypeVita:"aGVhZGNvbnRlbnQ=", - sAssetSource:"", - sIsDegraded:"false", - sPostActionUrl:"/news/common/maple/en-US/mwphucmtpost", - vitaMeta:{ - "vmeta_mandatory":{ - "asset_type": "article" , - "prop_source": "y.news", - "target_return_url":"http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" - } , - "vmeta_trg_imgt_opt":{ - "target_img_url": "", - "target_img_height": "0", - "target_img_width":"0", - "target_img_rights":"public" - } , - "vmeta_trg_title_opt":{ - "target_title": "GM+expects+competitive+Chevy+Volt+pricing+in+China", - "target_url": "http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china" - } , - "vmeta_testing":{ - "update_variant": "", - "update_transform": "" - }, - "vmeta_article_opt":{ - "article_source": "", - "article_author": "", - "article_description":"General+Motors+expects+competitive+pricing+for+its+electric+Chevrolet+Volt+in+China+as+it+hopes+to+gain+a+foothold+in+China%26%2339%3Bs+fledgling+environmentally+friendly+car+industry+with+the+highly+anticipated+car." - } - }, - oL10n:{ - "CHARACTERS_LEFT":"{0} characters remaining", - "CHARACTER_LEFT":"{0} character remaining", - "SIGN_IN":"Sign in", - "TO_RATE":"to rate", - "THANK_YOU":"Thank You!", - "COMMENTS_POST_SUCESS_PRV_MSG":"Your comment has been received and should be available shortly. A preview is shown here.", - "POST_ANOTHER_COMMENT":"Post Another Comment", - "POST_ANOTHER_REPLY":"Post Another Reply", - "A_SECOND_AGO":"a second ago", - "THANK_YOU":"Thank You!", - "COMMENTS_REPLY_SUCESS_PRV_MSG":"Your reply has been received and should be available shortly. A preview is shown here.", - "POST_GENERIC_ERROR":"We apologize. An error has occurred. Please try again.", - "FETCHING_UPDATES":"Fetching updates...", - "SERVER_ERROR":"Server Error", - "POSTING":"Posting", - "NO_COMMENTS_ERR":"Please enter a comment", - "REQUEST_FAILED":"An internal error has occurred", - "NO_UPDATE_AVAILAVLE":"No Updates available", - "SIGN_IN_TO_RATE":"Please sign in to rate!", - "RATE_SIGN_IN_TXT":"<p>Signing in ensures ratings are counted accurately and prevents system abuse.</p><p><a href=\"http://login.yahoo.com/config/login?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china\">Sign in to rate</a> or, <a href=\"http://edit.yahoo.com/config/eval_register?.src=yn&.intl=us&.done=http%3A%2F%2Fnews.yahoo.com%2Fs%2Fnm%2F20100831%2Fbs_nm%2Fus_gm_china\">sign up</a> for a new account.</p>", - "CLOSE_LINK_TXT":"Close this message", - "REPLY_LINK_TXT":"Reply", - "COLLAPSE_REPLIES":"Collapse Replies", - "VIEW_ALL_REPLIES":"View all {0} Replies", - "VIEW_ALL_REPLY":"View all {0} Reply", - "COMMENT_GUIDELINES":"Comment Guidelines", - "CANCEL_BTN_TXT":"Clear", - "POST_REPLY":"Post Reply", - "REPLY_TO_CONVERSTION":"Reply to Above Conversation", - "PLZ_ENTER_COMMENT":"Please Enter a Comment", - "ANONYMOUS_DISP_TXT":"A Yahoo! User", - "POST_CMT_DISABLED":"Comments have been closed for this article", - "REPLIES_LINK_TXT":"Replies" - } - ,bRecordPageviews:true, - nSpaceId:"" - - }); - YAHOO.namespace("phugcmaplecomt.container"); - - YAHOO.util.Event.addListener(window, "load", - function(){ - if(YAHOO.Updates != undefined){ - if(YAHOO.Updates.Disclosure.manager != undefined){ - YAHOO.Updates.Disclosure.manager.registerPostLinkCallback({"fn": - function() {YAHOO.Media.MwPhCom.coms.reValidateCrumb=true;}}); - } - } - }); - - </script> - - <!-- Ad Keywords: it; business; price; yuan; government; Yuan;--> -<!-- SpaceID=2023881070 loc=RICH noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['AmJLBkoGYmQ-']='&U=12c6qq6lb%2fN%3dAmJLBkoGYmQ-%2fC%3d-1%2fD%3dRICH%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1auc2qelt%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d307824506%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12c6qq6lb%2fN%3dAmJLBkoGYmQ-%2fC%3d-1%2fD%3dRICH%2fB%3d-1%2fV%3d0"></noscript> - - <!-- Ad Keywords: it; business; price; yuan; government; Yuan;--> -<!-- SpaceID=2023881070 loc=FSRVY noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['6mFLBkoGYmQ-']='&U=12dg4geec%2fN%3d6mFLBkoGYmQ-%2fC%3d-1%2fD%3dFSRVY%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1avo0rhda%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d3716110208%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12dg4geec%2fN%3d6mFLBkoGYmQ-%2fC%3d-1%2fD%3dFSRVY%2fB%3d-1%2fV%3d0"></noscript> - - - - <!-- Ad Keywords: it; business; price; yuan; government; Yuan;--> -<!-- SpaceID=2023881070 loc=UMU noad --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['A2JLBkoGYmQ-']='&U=12b0t7l5l%2fN%3dA2JLBkoGYmQ-%2fC%3d-1%2fD%3dUMU%2fB%3d-1%2fV%3d0'; -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1auafelf4%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3dYAHOO%2fF%3d114722436%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862&U=12b0t7l5l%2fN%3dA2JLBkoGYmQ-%2fC%3d-1%2fD%3dUMU%2fB%3d-1%2fV%3d0"></noscript> - -</body> -</html> - - - - - - -<!-- news:story-us:0:Success --> -<script language=javascript> -if(window.yzq_p==null)document.write("<scr"+"ipt language=javascript src=http://l.yimg.com/d/lib/bc/bc_2.0.4.js></scr"+"ipt>"); -</script><script language=javascript> -if(window.yzq_p)yzq_p('P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1aq2ijvp6%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d1.1%2fW%3dJ%2fY%3dYAHOO%2fF%3d2016075161%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fS%3d1%2fJ%3dF42A8862'); -if(window.yzq_s)yzq_s(); -</script><noscript><img width=1 height=1 alt="" src="http://us.bc.yahoo.com/b?P=PENKT2KIKjz0U5Bi.EoomABspUVaCEx9iNYABtx_&T=1avc6057a%2fX%3d1283295446%2fE%3d2023881070%2fR%3dnews%2fK%3d5%2fV%3d3.1%2fW%3dJ%2fY%3dYAHOO%2fF%3d2663170160%2fH%3dY2FjaGVoaW50PSJuZXdzIiBjb250ZW50PSJpdDsgYnVzaW5lc3M7IHByaWNlOyB5dWFuOyBnb3Zlcm5tZW50OyBZdWFuOyIgc2VydmVJZD0iUEVOS1QyS0lLanowVTVCaS5Fb29tQUJzcFVWYUNFeDlpTllBQnR4XyIgc2l0ZUlkPSI0NDY0MDUxIiB0U3RtcD0iMTI4MzI5NTQ0NjUzNjAxMSIg%2fQ%3d-1%2fS%3d1%2fJ%3dF42A8862"></noscript> -<!-- fe5.story.media.sp1.yahoo.com compressed Tue Aug 31 15:57:26 PDT 2010 --> diff --git a/src/test/resources/htmltests/yahoo-article-1.html.gz b/src/test/resources/htmltests/yahoo-article-1.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..852475fc60fa3b803cf97cb7b2fcbe7597f37fcf GIT binary patch literal 38736 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 Q)KI1UKUgIk&O>ej06g!81poj5 literal 0 HcmV?d00001 diff --git a/src/test/resources/htmltests/yahoo-jp.html b/src/test/resources/htmltests/yahoo-jp.html deleted file mode 100644 index 6aeb207b2d..0000000000 --- a/src/test/resources/htmltests/yahoo-jp.html +++ /dev/null @@ -1,602 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> -<html lang="ja"> -<head> -<meta http-equiv="content-type" content="text/html; charset=utf-8"> -<meta http-equiv="content-style-type" content="text/css"> -<meta http-equiv="content-script-type" content="text/javascript"> -<meta name="description" content="日本最大級のポータルサイト。検索、オークション、ニュース、メール、コミュニティ、ショッピング、など80以上のサービスを展開。あなたの生活をより豊かにする「ライフ・エンジン」を目指していきます。"> -<title>Yahoo! JAPAN</title> -<base href="http://www.yahoo.co.jp/_ylh=X3oDMTB0NWxnaGxsBF9TAzIwNzcyOTYyNjUEdGlkAzEyBHRtcGwDZ2Ex/"> -<style type="text/css"><!-- -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote{margin:0;padding:0;}fieldset,img{border:0;}table{border-collapse:collapse;border-spacing:0;}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:normal;}ol,ul{list-style:none;}.separate,.floatingw,legend{display:none;}button{cursor:pointer;}body{font-family:"MS PGothic","Osaka",Arial,sans-serif;line-height:1.22;font-size:12px;text-align:center;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;line-height:99%;}h1,h2,h3,h4,h5{font-size:100%;}.contentbox2nd h2,#spotlight h2{font-size:131%;}#emg table{font-size:115%;}span.assist,#topicsboxbd em,#topicsboxbd cite,#vdobd em{font-size:85%;}#contentbox h2,#csr h2,#local h2 span,#personalbox h3{font-weight:normal;}a,ul.tab a:visited{color:#1d3994;text-decoration:none;}a:visited{color:#941d55;}a:hover{text-decoration:underline;}button{cursor:pointer;}#wrapper{position:relative;min-width:950px;width:74.2em;*width:71.2em;margin:0 auto;}#header{position:relative;z-index:8;padding-bottom:4px;*zoom:1;}#header:after{content:"";display:block;clear:both;}#contents{text-align:left;overflow:hidden;*zoom:1;}#navi{float:left;position:relative;z-index:8;width:17.94%;min-width:170px;}#division{float:right;position:relative;z-index:2;min-width:770px;width:81%;}#main{float:left;min-width:410px;width:53.3%;}#sub{float:right;min-width:350px;width:45.45%;}.item li,.emphasis li,.connect li,.ranking li,.pldwn,.plup,.bgD,.iconNew,.iconPhoto,.iconVideo,.iconNotice,.close a,.ulmwindow .ulmwindowsearch button,.vdotmp2,#srchAssistOnOff dd,#pbfortune .floatingw li a,#pblogininfo li a,#yahooservice ul li a,#pbsocial p a,#pbsocialInfo,#pbsocial dd a,#topicsbox .tab li span,#yjidbox h2 a,#pbproperty .shortcut li a,#pbproperty .connect #mailicon,#srchSwitch dd,#contentbox .hd .cbbtn a,#changeMode,#changeMode a{background-image:url(http://k.yimg.jp/images/top/sp2/cmn/pic_all-091118.png);background-repeat:no-repeat;}.bx li{margin:7px 0;}.bx,.bxEx{position:relative;margin-bottom:10px;background-color:#fff;border:1px solid;}.bxEx{margin-top:-11px;}.bxNa{margin-bottom:10px;text-align:center;}.bxSl{overflow:hidden;*zoom:1;padding:7px 7px 3px;}.bxSl h2{margin-bottom:5px;float:left;clear:left;}.bxSl ul{margin-bottom:5px;overflow:hidden;*zoom:1;}.bxSl li{float:left;margin:0;padding-left:0.8em;}.bxSl .more{float:right;}.hd{border-bottom:1px solid;background-color:#fff;background-position:0 -200px;overflow:hidden;*zoom:1;}.hd h2{padding:3px 7px 2px;border:1px solid #fff;border-width:0 1px;}.hd h3{position:absolute;right:10px;top:3px;font-weight:normal;}.bgC{clear:both;padding:4px 5px 4px 10px;border:1px solid;}.bgC li{display:inline;margin-left:1em;}.bgC .first{margin-left:0;}.bgD{background-position:0 -2840px;background-repeat:repeat-x;}.bgI{background-color:#fefbc4;border:1px solid #ffcc01;}.close a{padding-right:20px;background-position:100% -1750px;color:#fff;cursor:pointer;text-decoration:underline;}.clfix{*zoom:1;}.clfix:after{content:"";display:block;clear:both;}.item li{padding-left:13px;background-position:0 -1447px;}.emphasis li{padding-left:13px;background-position:0 -1487px;}.connect li{display:inline;padding-left:13px;background-position:0 -1531px;}.connect .first{padding-left:0;background:none;}.symbol a{padding-left:12px;background-repeat:no-repeat;background-position:0 -872px;}.ranking li{padding-left:18px;}.ranking .rnk1{background-position:0 -2040px;}.ranking .rnk2{background-position:0 -2080px;}.ranking .rnk3{background-position:0 -2120px;}.ranking .rnk4{background-position:0 -2160px;}.ranking .rnk5{background-position:0 -2200px;}.ranking .rnk6{background-position:0 -2240px;}.ranking .rnk7{background-position:0 -2280px;}.ranking .rnk8{background-position:0 -2320px;}.ranking .rnk9{background-position:0 -2360px;}.ranking .rnk10{background-position:0 -2400px;}.pldwn,.plup{margin-right:2px;padding-right:13px;background-position:100% -1571px;background-repeat:no-repeat;color:#000;}.plup{background-position:100% -1611px;}.pldwn:visited,.plup:visited{color:#000;}.tab:after{content:"";display:block;clear:both;}.tab .on{font-weight:bold;}.tab .first{border-left:0;}.tab li a{outline:none;}.tab .on a,.tab .on a:visited{color:#000;text-decoration:none;}.assist a,.assist a:hover,.assist a:visited{display:block;padding:0 2px;text-decoration:none;}.assist{display:inline-block;border-style:solid;border-width:0 1px 1px 0;}.assist a,.assist a:visited{border:1px solid #9baab1;background:#fff;color:#000;}.assist a:hover{background-color:#ffeb7d;}.imgfilter{display:block;background:no-repeat 0 0;text-indent:-9999px;overflow:hidden;}.bkNum{padding:6px 10px 4px;}.bkNum h3{display:none;float:left;margin:0;}.bkNum dl{float:right;}.bkNum dt,.bkNum dd{float:left;line-height:1;margin-left:3px;}.bkNum dt{padding-top:2px;}.bkNum dd a{display:block;background-color:#fff;padding:2px 4px 1px 5px;outline:none;}.bkNum dd a:hover{text-decoration:none;background-color:#1d3994;color:#fff;}.bkNum dd a.on{background-color:#eee;color:#000;font-weight:bold;cursor:default;}.bkNum dd a.on:hover{background-color:#eee;color:#000;}.overlay{position:absolute;z-index:9;background:#000;filter:alpha(opacity=10);-ms-filter:"alpha(opacity=10)";opacity:0.1;}.iconNew,.iconPhoto,.iconVideo,#contentbox ul li .iconNotice{display:inline-block;margin-left:2px;text-indent:-9999px;outline:none;*vertical-align:middle;}.iconNew{background-position:0 -57px;height:11px;width:25px;}.iconPhoto{background-position:-37px -60px;height:15px;width:15px;margin-bottom:-1px;}.iconVideo{background-position:0 -78px;height:15px;width:15px;}#contentbox ul li .iconNotice{background-position:0 -1913px;padding:0;width:16px;height:14px;}#hdBar{position:absolute;z-index:9;top:-1.5em;margin-top:-1px;left:0;width:100%;}#masthead{position:relative;z-index:1;margin-top:1.6em;*margin-top:1.4em;*zoom:1;}h1{position:relative;z-index:5;width:950px;margin:0 auto;text-align:center;min-height:82px;}h1 img{margin:11px 0 10px;}h1 .deco{margin:1px 0;}#mhicon{position:absolute;left:50%;top:0;z-index:6;}#mhicon li{position:absolute;}#mhi1st{left:-365px;}#mhi2nd{left:-284px;}#mhi3rd{left:-203px;}#mhi4th{left:149px;}#mhi5th{left:230px;}#mhi6th{left:311px;}#mhicon li a{width:54px;height:54px;margin-top:15px;display:block;text-indent:-9999px;overflow:hidden;}#mhi1st a{background-position:0 -3900px;}#mhi2nd a{background-position:0 -3954px;}#mhi3rd a{background-position:0 -4008px;}#mhi4th a{background-position:0 -4062px;}#mhi5th a{background-position:0 -4116px;}#mhi6th a{background-position:0 -4170px;}#siteinfo{position:absolute;z-index:7;top:12px;right:10px;text-align:left;}#siteinfo li{padding-bottom:2px;}#emergency{margin:5px 0 10px;text-align:center;}#emergency .alert{color:#f00;}#emg{margin:0 auto;text-align:center;line-height:1.2;}#emg table{margin:0 auto 10px;}#emg table table{margin-bottom:0;}#emg br{display:none;}#searchbox{background-position:0 -1540px;*zoom:1;}#searchbox form{background-position:0 -1650px;background-repeat:no-repeat;}#searchbox fieldset{background-position:100% -1760px;background-repeat:no-repeat;}#srchbd{position:relative;width:48.8%;min-height:40px;margin:0 auto;padding:13px 0 20px;*padding:16px 0 17px;text-align:left;}#srchbd .tab{margin-bottom:5px;min-height:14px;_height:14px;}#srchbd .tab li{display:inline;padding:0 5px;border-left:1px solid #ccc;overflow:hidden;}#srchbd .tab li.first{padding-left:0;border-left:0;}#srchbd p{clear:both;overflow:hidden;}#srchtxtBg{line-height:0;width:74.4%;min-width:346px;display:block;float:left;border:solid #7c7c7c;border-width:1px 0 0 1px;*zoom:1;}#srchtxt{float:left;width:100%;_width:97.5%;*height:16px;min-height:16px;padding:2px 5px 3px;border:1px solid #c3c3c3;background-color:#fff;}#srchbtn{float:left;width:24.5%;padding:3px 0 2px;border:1px solid;background-position:0 -801px;font-weight:bold;letter-spacing:0.5em;min-height:23px;cursor:pointer;}#srchAssist{clear:left;width:74.8%;min-width:347px;position:absolute;top:73.5%;*top:77%;_top:74%;}#srchAssistBd{border:solid #7c7c7c;border-width:1px 2px 0 1px;background-color:#fff;*zoom:1;}#srchAssist li{line-height:1;}#srchAssist li a{color:#000;padding:4px 6px 5px;*padding:4px;display:block;cursor:pointer;*zoom:1;}#srchAssist li .on{background-color:#1d3994;color:#fff;}#srchAssistTxt{padding:3px 5px 3px 5px;}#srchAssistOnOff{padding:3px 8px 1px 0;white-space:nowrap;color:#555;font-size:85%;text-align:right;line-height:0;}#srchAssistOnOff dt{display:inline;line-height:1;}#srchAssistOnOff dd{display:inline;line-height:1;padding-left:13px;background-position:0 -1533px;font-weight:bold;color:#000;*zoom:1;}#srchAssistOnOff dd a{font-weight:normal;}#srchAssistOnOff dd.first{background:none;}#srchAssistClose,#srchacb,#srchAssistClose span{display:block;height:11px;background-repeat:no-repeat;cursor:pointer;overflow:hidden;line-height:0;text-indent:-9999px;}#srchAssistClose{background-position:100% -4269px;}#srchacb{background-position:0 -4225px;}#srchAssistClose span{margin:0 2px 0 45px;background-repeat:repeat-x;background-position:100% -4247px;}#srchAssistClose.on{background-position:100% -4280px;}.on #srchacb{background-position:0 -4236px;}#srchAssistClose.on span{background-position:100% -4258px;}#uhd{position:relative;height:1.5em;border:1px solid;text-align:center;}#uhdsetstart{position:absolute;left:5px;padding-top:2px;}#uhdassist{position:absolute;right:5px;}#clrEx{float:left;margin:3px 5px 0 0;padding-right:5px;border-right:1px solid #ccc;line-height:1;}#clr{float:left;}#clr li{float:left;margin:3px 5px 0 0;border-style:solid;border-width:1px;line-height:0;}#clr li a{display:block;*float:left;width:6px;height:6px;border-style:solid;border-width:2px;overflow:hidden;text-indent:-9999px;}#clr1{border-color:#a2b6d8;}#clr1 a{background-color:#c8d2e7;border-color:#c8d2e7;}#clr2{border-color:#d49fc9;}#clr2 a{background-color:#fbcaf0;border-color:#fbcaf0;}#clr3{border-color:#ffbc6d;}#clr3 a{background-color:#ffdaa4;border-color:#ffdaa4;}#clr4{border-color:#4ec346;}#clr4 a{background-color:#95da75;border-color:#95da75;}#clr5{border-color:#bdbdbd;}#clr5 a{background-color:#d6d6d6;border-color:#d6d6d6;}#clr6{border-color:#c4defa;}#clr6 a{background-color:#edf4f8;border-color:#edf4f8;}#clr1 .on,#clr1 a:hover{background-color:#6179a0;}#clr2 .on,#clr2 a:hover{background-color:#ef64c8;}#clr3 .on,#clr3 a:hover{background-color:#ff882b;}#clr4 .on,#clr4 a:hover{background-color:#1e880b;}#clr5 .on,#clr5 a:hover{background-color:#737373;}#clr6 .on,#clr6 a:hover{background-color:#c8d2e7;}#uhdassist .help{float:left;margin-top:2px;}#uhdassist .help a{border-left:1px solid #ccc;padding-left:5px;line-height:1em;}#toptxt{position:relative;z-index:7;margin:0 0 8px -1.8em;text-align:center;}#toptxt li{display:inline;margin-left:1.8em;}#navi #contentbox{border-top:0;background-position:0 -2800px;}#contentbox .hd{border-top:1px solid;position:static;}#contentbox .hd .cbbtn{float:left;margin-top:1px;padding:1px;border-right:1px solid;}#contentbox .hd .cbbtn span{border-right:1px solid #fff;}#contentbox .hd .cbbtn a{display:block;width:10px;height:10px;padding:3px 4px;text-indent:-9999px;overflow:hidden;}#cbbtntop{background-position:0 -110px;}#cbbtnbtm{background-position:-20px -110px;}#contentbox .changepos h2{padding-left:25px;}#contentbox .hd span.assist{float:right;margin-top:2px;margin-right:1px;}#contentbox ul{padding:2px 3px 0;*zoom:1;}#contentbox ul li{margin:1px 0;padding:3px 0;}#contentbox ul li a{padding:3px 0 3px 20px;background-repeat:no-repeat;}.cbysC1{background-position:0 -251px;}.cbysC2{background-position:0 -291px;}.cbysC5{background-position:0 -331px;}.cbysC12{background-position:0 -371px;}.cbysC13{background-position:0 -412px;}.cbysC14{background-position:0 -452px;}.cbysC15{background-position:0 -492px;}.cbysC25{background-position:0 -532px;}.cbysC26{background-position:0 -211px;}.cbysC33{background-position:0 -572px;}.cbysC34{background-position:0 -612px;}.cbysC41{background-position:0 -652px;}.cbysC73{background-position:0 -692px;}.cbysC48{background-position:0 -730px;}.cbysC37{background-position:0 -770px;}.cbysC53{background-position:0 -812px;}.cbysC57{background-position:0 -852px;}.cbysC46{background-position:0 -892px;}.cbysC44{background-position:0 -930px;}#favoriteservice ul li a{background-position:0 50%;}#cb2bgcx{position:absolute;left:0;*left:-2px;top:-1px;z-index:1;border:0;overflow:hidden;}#cb2bg{position:absolute;top:0;right:0;min-width:590px;width:46em;border:2px solid #ccc;border-width:0 2px 2px 0;background-color:#fff;}#checknumber,.cb2moreservice{width:100%;}.cb2ndhd{min-height:4em;_height:3.7em;}#checknumber .checkmax,.cb2moreservice .changemode{float:left;padding-left:10px;}#checknumber .checknow,.cb2moreservice .more{float:right;padding-right:10px;}.cb2moreservice{height:2.7em;*background-color:#fff;}.cb2moreservice .changemode{font-weight:bold;}.contentbox2nd{border:1px solid;color:#fff;background-position:0 -2050px;background-color:#fff;}.contentbox2nd h2{padding:5px 10px;color:#fff;}.contentbox2nd strong{color:#f00;}.contentbox2nd .cb2detail{margin:0 10px;border:1px solid #ccc;background:#fff;color:#000;}.cb2allservice{position:relative;z-index:2;padding:0 2px 5px 2px;background:#f0f3fa;overflow:hidden;*zoom:1;}#cb2yjedit .cb2allservice{background:#fffddb;}.contentbox2nd h3{clear:both;padding:4px 0 3px;}.cb2allservice ul li,#cb2worldservice ul li{position:relative;z-index:1;display:inline;float:left;width:20%;margin-bottom:3px;}#cb2yjedit .cb2allservice ul{*margin-top:-3px;*margin-bottom:4px;margin-bottom:3px;}#cb2yjedit .cb2allservice ul li{*margin-bottom:-3px;}.cb2allservice ul li *{vertical-align:middle;}.cb2allservice ul li label *{*vertical-align:baseline;}.cb2allservice ul li.on{color:#f00;font-weight:bold;*letter-spacing:-1px;}.cb2allservice ul li.off{color:#999;}.cb2allservice ul li.off input{visibility:hidden;}.cb2etc{padding:0 2px;}.cb2moreservice{clear:both;}.contentbox2nd span.close a{position:absolute;top:5px;right:8px;color:#fff;}#cb2yjservice .cb2detail ul li a{margin-left:15px;}#cb2worldservice{_height:71px;min-height:71px;}#cb2yjedit input{width:15px;}#cb2selectarea{position:relative;min-height:61px;_height:71px;padding-top:10px;}#cb2selectarea li{margin-bottom:5px;}#cb2selectarea button{width:7.8em;padding:1px 0;*padding:0;border:0;border-top:1px solid #fff;font-weight:bold;text-align:center;}#cb2selectarea p{position:absolute;bottom:10px;right:10px;}#cb2selectarea span{display:block;float:left;}#cb2cancelbg{position:relative;right:5px;}#cb2selectarea span#cb2cancelbg,#cb2selectarea button#cb2cancel{background:#ccc;}#cb2selectarea span#cb2cancelbg{border:1px solid #666;}#cb2selectarea span#cb2setupbg,#cb2selectarea button#cb2setup{background:#fc3;}#cb2selectarea span#cb2setupbg{border:1px solid #ce8800;}#cb2bg .off #cb2selectarea span#cb2setupbg{border-color:#d1d1d1;}#cb2bg .off #cb2selectarea span#cb2setupbg,#cb2bg .off #cb2selectarea span#cb2setupbg button{background:#ddd;color:#999;}#navi #cb2popup{position:absolute;z-index:9;padding-bottom:3px;border:0;background:transparent;width:12em;margin:-7.7em 0 0 -1.05em;letter-spacing:-0.8px;}#cb2popup p{padding:4px 0 4px 2px;border:3px solid #b9c6d3;background:#fff;color:#000;font-weight:normal;}#cb2popup p strong{color:#e72e00;font-weight:bold;}#cb2popup p span,#cb2popup .cb2pbg2{background:url(http://k.yimg.jp/images/top/sp2/cb/cb2p_bg-090407.gif) no-repeat 0 0;}#cb2popup p span{position:absolute;right:2px;*right:1px;top:-6px;display:block;width:17px;height:13px;text-indent:-9999px;cursor:pointer;}#cb2popup .cb2pbg{margin:0 2px -3px 0;}#cb2popup .cb2pbg2{padding-bottom:9px;background-position:-20px 100%;}#csr{background-position:0 -3100px;}#csr ul{padding:0 0 0 6px;}#companybox{padding:0 2px;background-position:0 -930px;}#companybox div{border-top:1px solid;}#companybox h2{padding:5px 5px 0;border-top:1px solid #fff;}#companybox ul,#companybox p{margin:0 0 0 4px;overflow:hidden;*zoom:1;}#companybox #cmprikunabi,#companybox #cmprikunabi h2{border-top:0;}#cmprikunabi ul{margin-top:7px;margin-bottom:7px;}#cmprikunabi ul li{display:inline;margin-left:0.5em;}#cmprikunabi ul li.first{margin-left:0;}#cmprikunabi p{margin-bottom:7px;}#composite li{margin-bottom:10px;*margin-bottom:8px;line-height:0;}#composite .exception{margin-top:-10px;}#composite li a{display:block;text-indent:-9999px;width:170px;height:40px;margin:auto;overflow:hidden;background-image:url(http://k.yimg.jp/images/top/sp2/cmp/comp_all-091228.png);}#composite li a#cmpSl{background-position:0 -40px;}#composite li a#cmpElc{background-position:0 -80px;}#composite li a#cmpOlym{background-position:0 -120px;}#topicsbox{border-top:none;}#topicsbox .hd{margin:0 -1px;border-bottom:0;}#topicsbox h2{display:none;}#topicsbox .tab{position:relative;z-index:1;padding-bottom:1px;border:1px solid;background-position:0 -100px;}#topicsbox .tab li{position:relative;float:left;width:20%;margin:-1px 0 -2px;border-bottom:1px solid;*border-bottom:0;border-top:1px solid;background-position:0 -100px;text-align:center;}#topicsbox .tab li span{display:block;position:relative;z-index:9;margin-bottom:-1px;border-right:1px solid;background-position:100% 0;*zoom:1;}#topicsbox .tab li span a{display:block;position:relative;margin-right:-2px;*margin-bottom:-1px;padding:1px 2px 2px 0;border:1px solid #fff;*zoom:1;}#topicsbox .on1 .tab0 span a,#topicsbox .on2 .tab1 span a,#topicsbox .on3 .tab2 span a,#topicsbox .on4 .tab3 span a,#topicsbox .tab .on span a,#topicsbox .tab .last span a{margin-right:0;padding-right:0;}#topicsbox .tab .tab1 span a{*border-left:0;}#topicsbox .on4{border-bottom-color:#fff;border-right-color:#9baab1;background-position:0 -300px;background-color:#fff;padding-bottom:2px;*padding-bottom:1px;}#topicsbox .tab .on{z-index:9;padding:0;margin-bottom:-2px;*margin-bottom:-1px;background-position:0 -300px;background-color:#fff;}#topicsbox .tab .on span{*padding-bottom:1px;border-right:1px solid;border-bottom:1px solid #fff;}#topicsbox .tab .off span{border-right:1px solid;*border-bottom:1px solid;}#topicsbox .tab .last{*width:19.7%;border-bottom:0;}#topicsbox .tab .last span,#topicsbox .tab .last span a{border-right:0;}#topicsbox .on4 .on span{border-right:0;}#topicsboxbd{min-height:220px;padding-right:10px;overflow:hidden;_overflow:visible;*zoom:1;}#topicsboxbd div{display:none;}#topicsboxbd div div,#topicsboxbd .current{display:block;}.topicsindex{float:left;width:63.5%;}.topicsindex em{margin:6px 0 0 8px;}.topicsindex .emphasis{margin:6px 0 0 5px;}.topicsindex .emphasis li,#othersfb .detail li{margin:5.5px 0 5px;}.topicsindex .emphasis li,#othersfb .detail li, x:-moz-any-link{max-height:1.23em;}.topicsindex .emphasis li img{margin-left:2px;}.topicsindex .more{margin:12px 15px;}.topicsindex .more li{display:inline;padding-right:1em;}.topicscatch{float:right;width:35.2%;margin-top:10px;padding-top:1px;}#topicsbox .topicsdetail{padding:3px 5px 3px 9px;margin:-1px 0 0;}#topicsboxbd .topicsdetail{border:1px solid;}.topicsimg{margin:6px 0 0 -4px;padding-bottom:2px;text-align:center;}#tpcsimgfilter{margin:auto;}.topicscatch h3,.topicscatch ul li{margin:2px -3px 0 0;padding-bottom:2px;}.topicscatch p{margin-bottom:2px;padding:2px 0;}#topicsbox em,#topicsbox .topicsdetail cite{display:block;color:#666;}#topicsbox .topicsdetail cite{margin-bottom:5px;}.topicscatch .item{padding:1px 0 2px;margin-left:-5px;}.topicscatch .mds{margin-bottom:5px;}.tpcdtlinfo{position:relative;padding-bottom:4px;}.tpcdtlinfo dt,.tpcdtlinfo dd{line-height:1.1em;*line-height:1em;}.tpcdtlinfo dt{margin-top:4px;}.tpcdtlinfo dd{text-align:right;margin-right:4px;}.tpcdtlinfo dt.ex,.tpcdtlinfo dt.last{position:absolute;left:0;}.tpcdtlinfo dd.ex,.tpcdtlinfo dd.last{margin-top:4px;margin-left:3em;}.tpcdtlinfo dd.low{color:#f00;}#othersfb{padding:5px 0 0 10px;*margin-right:-10px;}#othersfb .detail{float:left;width:50%;*width:49.8%;}#othersdetail3{clear:both;padding-top:10px;overflow:hidden;}#othersdetail3 h3{float:left;padding-right:14px;}#othersdetail3 h4{margin:0 -10px 8px 0;font-weight:normal;}#topicsbox .notfound{_height:auto;min-height:1em;}.notfound #topicsfb p{color:#666;line-height:1.7em;margin:20px 16px 20px;}.notfound #topicsfb p strong{margin-left:-6px;}.notfound #topicsfb p a{text-decoration:underline;}#topicsInfo dt a{display:block;width:46px;height:46px;background-position:0 -200px;text-indent:-9999px;overflow:hidden;line-height:0;}#spotlight{padding:10px 10px 0;background-position:0 -300px;*zoom:1;}#splsentence{float:left;width:61%;}.spltmp3 #splsentence{margin-bottom:2em;}.spltmp3 #splsentence h2{margin-bottom:0.5em;}#spotlight .nonImg{width:auto;}#splsentence p{margin:10px 4px;line-height:1.5;*line-height:1.4;}#splimg{float:right;width:142px;padding-bottom:5px;text-align:center;}#splimgfilter{width:142px;height:100px;}#spotlight ul{clear:both;margin:0 9px 9px;overflow:hidden;*zoom:1;}#spotlight ul li{float:left;width:50%;margin:3px -1px 3px 0;}#spotlight .mds{margin:0 12px 2px;}#spldetail{clear:both;margin-bottom:10px;padding:3px 5px;text-align:center;}#splBkNum{margin:0 -10px;}#eventPromo{padding:11px 8px;overflow:hidden;*zoom:1;}#eventPromo .img{float:left;padding-right:8px;}#eventPromo p{margin:4px 0 0 178px;line-height:1.4em;}#selectionR #slcbd{position:relative;min-height:125px;_height:125px;overflow:hidden;_overflow:visible;}#selectionR .slcImg{padding:5px;}#selectionR li{margin:0 0 7px;*zoom:1;}#selectionR #slcbd,#selectionR h4{padding:4px 5px;*padding-bottom:8px;}#selectionR p{margin:4px 5px;}#selectionR h4,#selectionR ul{margin-bottom:4px;padding-right:0;}#selectionR h5.f2b a{padding:2px 0 2px 18px;background-repeat:no-repeat;background-position:0 50%;}#selectionR .slctmpR2 h4,#selectionR .slctmpR12 h4{padding-bottom:0;}#selectionR .slctmpR11{margin-left:4px;}#selectionR .slctmpR12{padding-right:0;}#selectionR .slctmpR12 p{margin-top:0;}#selectionR .slctmpR13 h4{padding-right:5px;}#selectionR .slctmpR13 ul{clear:left;}#selectionR .slctmpR15 .slcImg{padding:5px 0;text-align:center;}.al{float:left;padding-right:10px;}.ar{float:right;}.ac{text-align:center;}.pa{position:absolute;left:5px;}.c2{width:50%;*width:49.9%;float:left;}.c2b{width:50%;*width:39.5%;float:left;}.c3{float:left;width:33.33%;*width:33%;}.c3b{width:33.33%;*width:25%;float:left;}.c4{width:25%;*width:24.9%;float:left;}.f1{margin-left:83px;}.f2b{margin-left:129px;}#vdobd{overflow:hidden;*zoom:1;}#vdobd .imgfilter{width:120px;height:90px;}#vdobd em{color:#666;line-height:1.22;}.vdotmp1b{position:relative;padding:2px 10px 6px;}.vdotmp1b em{float:left;display:block;margin-top:103px;width:120px;}.vdotmp1b em span{vertical-align:bottom;}.vdotmp1b .img{position:absolute;top:10px;left:9px;border:1px solid #666;padding:1px;}.vdotmp1b .symbol{padding:0 0 1.5em 130px;*margin:10px 0 0 -3px;}.vdotmp1b .more{position:absolute;right:10px;bottom:7px;line-height:1.1;}.vdotmp2{padding:4px 0 4px 11px;background-position:0 -2440px;background-repeat:repeat-x;}.vdotmp2 ul{*zoom:1;*margin-right:-2px;}.vdotmp2 ul li{float:left;width:33.3%;margin-bottom:0;}.vdotmp2 ul li .imgfilter{margin:0 auto 22px;}.vdotmp2 ul li .img,.vdotmp2 ul li p,.vdotmp2 ul li em{padding-right:10px;}#cgmboxR #cgmbd{padding:9px 8px 0;overflow:hidden;*zoom:1;}#cgmboxR h3{margin-bottom:8px;}#cgmboxR p{padding-bottom:10px;line-height:1.5em;}#cgmboxR img{float:left;}.cgmtmpR2b ul{margin:-7px 0 0 127px;}.cgmtmpR3 ol,.cgmtmpR4 ul,.cgmtmpR10 ul,.cgmtmpR13 ol{float:left;width:50%;*width:49.8%;margin:-7px 0 1px;overflow:hidden;*zoom:1;}#cgmboxR .cgmtmpR5 li p,#cgmboxR .cgmtmpR6 li p{margin:2px 12px 0;padding-bottom:0;line-height:1.2em;*line-height:1.1em;}.cgmtmpR6 ul{margin-left:108px;}.cgmtmpR7 img,.cgmtmpR11 img{clear:both;}.cgmtmpR7 h4,.cgmtmpR11 h4{margin-bottom:10px;font-weight:normal;}.cgmtmpR7 .detail,.cgmtmpR11 .detail{_height:84px;min-height:84px;margin-left:110px;}.cgmtmpR12 p{margin-bottom:-5px;}.cgmtmpR13 ol{*padding-bottom:2px;}#announce .item{margin:8px 0 0 3px;}#sub #brandpanel{position:relative;z-index:3;border:0;background-color:transparent;text-align:center;}#sub .yzq_x{left:-9999px;}#personalbox{z-index:2;padding:5px 5px 0;background-position:0 100%;*zoom:1;}#personalbox h2{display:none;}#pbproperty .connect,#personalbox h3 span,#personalbox #pbdata{font-weight:bold;}#pbidinfo,#pbproperty{position:relative;*zoom:1;}#pbidinfo li{margin:3px 0 0;}#pbidinfo .loginout{position:absolute;top:2px;}#pbidinfo .info{position:absolute;right:0;top:0.5em;}#pbidinfo .info span{position:absolute;right:0;width:5em;top:-0.3em;text-align:center;}#pbproperty{margin-top:2.2em;border:1px solid;background:#fff;*zoom:1;}#pbproperty .connect{padding:4px 0;margin:0 0 0 4px;}#pbproperty .connect #mailicon{position:absolute;display:block;width:36px;height:27px;float:left;margin-top:-7px;text-indent:-9999px;overflow:hidden;background-position:0 -130px;}#pbproperty .connect #mailicon.on{background-position:0 -160px;}#pbproperty .connect .txt{padding-left:40px;}#pbproperty .shortcut{position:absolute;top:50%;right:5px;margin-top:-8px}#pbproperty .shortcut li{float:left;padding-left:8px;margin:0;}#pbproperty .shortcut li a{display:block;overflow:hidden;text-indent:-9999px;width:16px;height:16px;background-position:0 -94px;}#pbproperty .shortcut li a.second{background-position:-19px -94px;}#pbproperty .shortcut li a.third{background-position:-37px -94px;}#pbindex{position:relative;z-index:2;background-position:0 -600px;*zoom:1;}#pbindex li{margin:0;}#pbindexbg{position:relative;z-index:2;margin-top:2px;padding:1px 2px 3px;border:1px solid;background:#fff;}#pbtoday{position:relative;padding:0 47% 0 2px;*padding-right:4px;*zoom:1;}#pbtoday:after{content:"";display:block;clear:both;}#pbweather,#pbplan,#pbfortune{border-top:1px solid;*zoom:1;}h3#pbdata{padding:3px 0;text-align:center;}#pbweather{position:relative;z-index:9;}#pbweather.grayout{*zoom:1;line-height:1.3;}#pbweather .img{position:absolute;display:block;width:175px;height:48px;top:0;margin-top:-5px;*margin-top:-3px;text-indent:-9999px;background-position:-250px -250px;}#pbweather.grayout #pbwlocation{padding-top:4px;*padding-top:1px;}#pbweather,#pbplan,#pbfortune{padding:3px 0;clear:left;}#pbweather h3{float:left;}#pbwarea{text-indent:10px;}#pbwicon{clear:left;float:left;}#pbwicon a{display:block;width:33px;height:20px;}#pbindex #pbwtemperature{float:right;margin-top:4px;padding-left:2em;border-left:1px solid #999999;line-height:1;}#pbwtemperature .high{color:#f00;}#pbwtemperature .low{color:#00f;}#pbwrprobability{margin-bottom:3px;padding:3px 15px 0 0;text-align:center;}#pbtoday .grayout h3{float:none;}#pbwlocation{clear:left;text-align:right;}#pbplan h3,#pbfortune h3{position:absolute;}#pbpnumber,#pbfconstellation{float:left;padding:0 0 3px 5.8em;}#pbfpoint{float:right;}#pbfortune{position:relative;z-index:5;}#pbfortune .floatingw{position:absolute;left:-5px;z-index:9;width:15em;margin-top:1em;padding:5px 5px 1px;border:1px solid #ccc;background:#fffac6;}#pbfortune .floatingw li{float:left;width:4.8em;padding:0 0.2em 0.4em 0;}#pbfortune .floatingw li a{padding-left:17px;background-repeat:no-repeat;}#aries{background-position:0 -972px;}#taurus{background-position:0 -1012px;}#gemini{background-position:0 -1052px;}#cancer{background-position:0 -1092px;}#leo{background-position:0 -1132px;}#virgo{background-position:0 -1172px;}#libra{background-position:0 -1212px;}#scorpio{background-position:0 -1252px;}#sagittarius{background-position:0 -1292px;}#capricorn{background-position:0 -1332px;}#aquarius{background-position:0 -1372px;}#pisces{background-position:0 -1412px;}#pbcalendar{*position:relative;*z-index:-1;*margin-top:1px;float:right;}#pbcbg{margin-top:1px;*margin-top:0;padding:1px 0 0 1px;border:1px solid #ccc;}#pbcalendar table{position:relative;z-index:1;border:1px solid #fff;}#pbcalendar table td{padding:2px 2px 0 3px;*padding:2px 3px 1px 3px;text-align:right;}#pbcalendar table td a{display:block;min-width:16px;}#personalbox table td .e{color:#999;}#personalbox table td .h{color:#f00;}#pbcalendar table td.t a{color:#fff;}#pbsocial{margin-top:5px;padding:4px 8px;*padding:3px 8px 5px;background:#fff;border:1px solid;overflow:hidden;*zoom:1;}#pbsocial p a{background-position:0 -1832px;display:block;float:left;margin:-1px 0;padding:2px 0 2px 20px;min-height:13px;}#pbsocial dl{margin:0 -5px;}#pbsocialInfo{float:left;width:56%;padding-top:2px;background-position:0 -1873px;}#pbsocial dt a{padding-left:16px;}#pbsocial dd{float:right;*margin-top:2px;background-image:url(http://k.yimg.jp/images/top/sp2/pb/vitality_bg-091118.png);background-repeat:no-repeat;*zoom:1;}#pbsocial dd{padding-left:5px;}#pbsocialNotice{border-left:1px solid #999;}#pbsocial dd.level0{background-position:5px 2px;}#pbsocial dd.level1{background-position:5px -38px;}#pbsocial dd.level2{background-position:5px -78px;}#pbsocial dd.level3{background-position:5px -118px;}#pbsocial dd.level4{background-position:5px -158px;}#pbsocial dd.level4x{background-position:5px -198px;}#pbsocial dd a{display:block;width:65px;text-indent:-9999px;overflow:hidden;}#pbsocialNotice a{background-position:6px -1913px;}#pbsocialMsg a{background-position:6px -1953px;}#pblogininfo{margin:5px -4px 0;border-top:1px solid;*zoom:1;}#pblogininfo ul{padding-left:12px;border-top:1px solid #fff;}#pblogininfo ul li{margin:3px 0;}#pblogininfo li a{padding:2px 0 2px 20px;background-repeat:no-repeat;min-height:13px;display:inline-block;}#pblogininfo .point{float:left;width:54%;border-right:1px solid #999;}#pblogininfo .star{float:right;width:43%;}#pblogininfo .point a{background-position:0 -1652px;}#pblogininfo .star a{background-position:0 -1992px;}#pblogininfo .login a{background-position:0 -1692px;}#yjidbox{position:relative;*zoom:1;background-position:0 -930px;}#yjidbox{min-height:68px;padding:5px 10px;}#yjidbox h2{float:left;margin-right:1.2em;}#yjidbox h2 a{background-position:0 -1793px;padding:2px 0 2px 20px;*zoom:1;}#yjidbox .more{text-align:right;}#yjidbox img{position:absolute;left:10px;margin-top:5px;}#yjidbox ul{margin:10px 0 12px 60px;}#sub #yjidboxB{border:none;background:none;text-align:center;}#everywhere .item{margin:8px 0 0 3px;}#everywherePromo{margin:10px;text-align:center;}#notice ul{padding:0 10px;}#rightbox{overflow:hidden;*zoom:1;}#rightbox #rbdtl3{margin:8px 0 0 3px;}#rightbox #rbdtl3 li{*zoom:1;}#rightbox #rbimg{float:left;}#rightbox #rbimg a{outline:none;}#rightbox #rbimg a img{border:1px solid #ccc;}#rightbox #rbimg img,#rightbox #rbimg2 img{margin:10px;}#rightbox #rbimg img{*margin-bottom:0;}#rightbox #rbdtl{margin:5px 10px;}#rightbox #rbdtl2{margin:13px 10px;}#rightbox #rbdtl a,#rightbox #rbdtl2 a{line-height:1.8em;*line-height:1.6em;}#sub #rightbox2{border-top:0;margin-top:-10px;padding:7px 10px;}#local{z-index:9;*zoom:1;}#localbd{overflow:hidden;*zoom:1;}#localbd .item{margin:8px 0 0 3px;}#localbd dl{clear:both;*zoom:1;margin:0 10px 5px;padding-top:5px;}#localbd dt{display:inline;padding:1px 2px;background-color:#ff7301;color:#fff;font-size:85%;}#localbd dd{display:inline;margin-left:1em;}#localbd .more{clear:both;*zoom:1;text-align:right;padding:4px 10px;}#partner .item{margin:8px 0 0 3px;}#partner p.more{margin:0 10px 10px;text-align:right;}#footer{padding:5px 0;margin-bottom:0;}#footer ul,#footer address{margin:5px auto;}.ulmwindow{position:absolute;left:-11px;z-index:9;display:block;min-width:328px;width:24.6em;margin-top:-2px;padding:10px;border:1px solid #6990b4;background:#fffac6;color:#666;}.ulmwindow form{position:relative;*zoom:1;}body .ulmwindow .alert{color:#f00;}.ulmwindowttl{margin:10px 0 -6px;}.ulmwindowdtl{height:100px;margin-top:11px;padding:1px 0;border:1px solid #ccc;background:#fff;overflow:auto;}ul.ulmwindowdtl li{margin:3px 0;}ul.ulmwindowdtl li a{display:block;margin:0 1px;padding:1px 0 1px 0.5em;}.ulmwindow .ulmwindowbd{padding:8px 9px;border:1px solid #ccc;background:#fffac6;}.ulmwindow label{display:block;}.ulmwindow .ulmwindowCth{margin-bottom:4px;color:#000;line-height:1.5;}.ulmwindow .ulmwindowmds{margin-bottom:7px;color:#666;}.ulmwindow .ulmStart .ulmwindowmds{margin-bottom:2px;}.ulmwindowmds span{display:block;margin:3px 0 0 1em;}.ulmwindow .ulmwindowsearch *{vertical-align:middle;}.ulmwindow .ulmwindowarea{position:relative;}.ulmwindow .ulmwindowsearch{vertical-align:middle;padding-bottom:3px;}.ulmwindow .ulmwindowsearch input{width:77%;min-height:16px;*height:17px;margin-right:3px;padding:1px 3px;border:1px solid #7f9db9;}.ulmwindow .ulmwindowsearch button{padding:0 10px;*padding:0 6px;*height:21px;border:1px solid;background-position:0 -2730px;}.ulmwindow .ulmwindowsearch .ulmwindowsrchbtn0{border-color:#666767;background-color:#ccc;}.ulmwindow .ulmwindowsearch .ulmwindowsrchbtn{border-color:#57718f;background-color:#57718f;background-position:0 -2700px;color:#fff;}.ulmwindow .ulmwindowevery{margin:6px 0 0 4em;}.ulmwindow .ulmwindowevery input{margin-right:1em;}.ulmwindow .ulmwindowevery *{*vertical-align:middle;}.grayout #pbweatherfw{left:-11px;}#pbweatherfw .ulmwindowCth{background-position:0 -97px;}#localfw{top:1.6em;left:-1px;}#localfw2{position:static;width:auto;margin:0;border-width:0 0 1px;}#localfw2 .ulmwindowCth{background-position:0 -250px;}#changeMode{position:absolute;bottom:13px;left:0;z-index:5;border:1px solid #b4bdc3;background-color:#e5eaeb;background-position:0 -2760px;background-repeat:repeat-x;}#changeMode a{display:inline-block;padding:4px 9px 1px 28px;*padding:3px 6px 2px 28px;background-position:0 -2800px;border-bottom:1px solid #fff;}#selectionR h5{padding-top:5px;font-weight:normal;}#selectionR img.ico{padding:1px;vertical-align:middle;}.f2{margin-left:155px;}.vdotmp1{position:relative;padding:2px 10px 6px;}.vdotmp1 em{float:left;display:block;margin-top:122px;width:140px;}.vdotmp1 .img{position:absolute;left:10px;top:2px;padding:15px 11px;background:url(http://k.yimg.jp/images/top/sp2/vdo/vdo_all-091208.png) no-repeat;}.vdotmp1 .symbol{padding:0 0 1.5em 148px;}.vdotmp1 .more{position:absolute;right:10px;bottom:7px;line-height:1.1;}.cgmtmpR2 ul{margin:-7px 0 0 151px;}.bxShp{overflow:hidden;padding-bottom:9px;}.bxShp img{float:left;margin:9px 9px 0;}.bxShp .item{margin:14px 0 0 66px;}.bxShp .item li{margin-top:8px;}#centralPosition{margin-top:-20px;border-top:0;}#centralPosition dl{margin:0 9px 0 67px;padding:9px 0 7px 0;background:url(http://k.yimg.jp/images/top/sp2/cmn/pic_all-091118.png) repeat-x 0 -2840px;}#centralPosition dt{position:absolute;left:9px;font-weight:bold;}#centralPosition dd{margin-left:-67px;padding-left:3.8em;} ---></style> -<script type="text/javascript"><!-- -var ver="ga3_fx"; -if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={}}YAHOO.namespace=function(){var a=arguments,b=null,d,e,c;for(d=0;d<a.length;d=d+1){c=(""+a[d]).split(".");b=YAHOO;for(e=(c[0]=="YAHOO")?1:0;e<c.length;e=e+1){b[c[e]]=b[c[e]]||{};b=b[c[e]]}}return b};YAHOO.log=function(b,a,c){var d=YAHOO.widget.Logger;if(d&&d.log){return d.log(b,a,c)}else{return false}};YAHOO.register=function(d,i,a){var e=YAHOO.env.modules,c,f,g,h,b;if(!e[d]){e[d]={versions:[],builds:[]}}c=e[d];f=a.version;g=a.build;h=YAHOO.env.listeners;c.name=d;c.version=f;c.build=g;c.versions.push(f);c.builds.push(g);c.mainClass=i;for(b=0;b<h.length;b=b+1){h[b](c)}if(i){i.VERSION=f;i.BUILD=g}else{YAHOO.log("mainClass is undefined for module "+d,"warn")}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(a){return YAHOO.env.modules[a]||null};YAHOO.env.ua=function(){var b={ie:0,opera:0,gecko:0,webkit:0,mobile:null,air:0,caja:0},c=navigator.userAgent,a;if((/KHTML/).test(c)){b.webkit=1}a=c.match(/AppleWebKit\/([^\s]*)/);if(a&&a[1]){b.webkit=parseFloat(a[1]);if(/ Mobile\//.test(c)){b.mobile="Apple"}else{a=c.match(/NokiaN[^\/]*/);if(a){b.mobile=a[0]}}a=c.match(/AdobeAIR\/([^\s]*)/);if(a){b.air=a[0]}}if(!b.webkit){a=c.match(/Opera[\s\/]([^\s]*)/);if(a&&a[1]){b.opera=parseFloat(a[1]);a=c.match(/Opera Mini[^;]*/);if(a){b.mobile=a[0]}}else{a=c.match(/MSIE\s([^;]*)/);if(a&&a[1]){b.ie=parseFloat(a[1])}else{a=c.match(/Gecko\/([^\s]*)/);if(a){b.gecko=1;a=c.match(/rv:([^\s\)]*)/);if(a&&a[1]){b.gecko=parseFloat(a[1])}}}}}a=c.match(/Caja\/([^\s]*)/);if(a&&a[1]){b.caja=parseFloat(a[1])}return b}();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var d=YAHOO_config.listener,a=YAHOO.env.listeners,b=true,c;if(d){for(c=0;c<a.length;c=c+1){if(a[c]==d){b=false;break}}if(b){a.push(d)}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var f=YAHOO.lang,b="[object Array]",e="[object Function]",a=Object.prototype,c=["toString","valueOf"],d={isArray:function(g){return a.toString.apply(g)===b},isBoolean:function(g){return typeof g==="boolean"},isFunction:function(g){return a.toString.apply(g)===e},isNull:function(g){return g===null},isNumber:function(g){return typeof g==="number"&&isFinite(g)},isObject:function(g){return(g&&(typeof g==="object"||f.isFunction(g)))||false},isString:function(g){return typeof g==="string"},isUndefined:function(g){return typeof g==="undefined"},_IEEnumFix:(YAHOO.env.ua.ie)?function(i,j){var k,g,h;for(k=0;k<c.length;k=k+1){g=c[k];h=j[g];if(f.isFunction(h)&&h!=a[g]){i[g]=h}}}:function(){},extend:function(h,g,i){if(!g||!h){throw new Error("extend failed, please check that all dependencies are included.")}var j=function(){},k;j.prototype=g.prototype;h.prototype=new j();h.prototype.constructor=h;h.superclass=g.prototype;if(g.prototype.constructor==a.constructor){g.prototype.constructor=g}if(i){for(k in i){if(f.hasOwnProperty(i,k)){h.prototype[k]=i[k]}}f._IEEnumFix(h.prototype,i)}},augmentObject:function(h,i){if(!i||!h){throw new Error("Absorb failed, verify dependencies.")}var l=arguments,j,g,k=l[2];if(k&&k!==true){for(j=2;j<l.length;j=j+1){h[l[j]]=i[l[j]]}}else{for(g in i){if(k||!(g in h)){h[g]=i[g]}}f._IEEnumFix(h,i)}},augmentProto:function(g,h){if(!h||!g){throw new Error("Augment failed, verify dependencies.")}var j=[g.prototype,h.prototype],i;for(i=2;i<arguments.length;i=i+1){j.push(arguments[i])}f.augmentObject.apply(this,j)},dump:function(o,j){var m,k,h=[],g="{...}",n="f(){...}",i=", ",l=" => ";if(!f.isObject(o)){return o+""}else{if(o instanceof Date||("nodeType" in o&&"tagName" in o)){return o}else{if(f.isFunction(o)){return n}}}j=(f.isNumber(j))?j:3;if(f.isArray(o)){h.push("[");for(m=0,k=o.length;m<k;m=m+1){if(f.isObject(o[m])){h.push((j>0)?f.dump(o[m],j-1):g)}else{h.push(o[m])}h.push(i)}if(h.length>1){h.pop()}h.push("]")}else{h.push("{");for(m in o){if(f.hasOwnProperty(o,m)){h.push(m+l);if(f.isObject(o[m])){h.push((j>0)?f.dump(o[m],j-1):g)}else{h.push(o[m])}h.push(i)}}if(h.length>1){h.pop()}h.push("}")}return h.join("")},substitute:function(g,u,n){var q,r,s,k,j,h,l=[],t,p="dump",m=" ",v="{",i="}",o;for(;;){q=g.lastIndexOf(v);if(q<0){break}r=g.indexOf(i,q);if(q+1>=r){break}t=g.substring(q+1,r);k=t;h=null;s=k.indexOf(m);if(s>-1){h=k.substring(s+1);k=k.substring(0,s)}j=u[k];if(n){j=n(k,j,h)}if(f.isObject(j)){if(f.isArray(j)){j=f.dump(j,parseInt(h,10))}else{h=h||"";o=h.indexOf(p);if(o>-1){h=h.substring(4)}if(j.toString===a.toString||o>-1){j=f.dump(j,parseInt(h,10))}else{j=j.toString()}}}else{if(!f.isString(j)&&!f.isNumber(j)){j="~-"+l.length+"-~";l[l.length]=t}}g=g.substring(0,q)+j+g.substring(r+1)}for(q=l.length-1;q>=0;q=q-1){g=g.replace(new RegExp("~-"+q+"-~"),"{"+l[q]+"}","g")}return g},trim:function(h){try{return h.replace(/^\s+|\s+$/g,"")}catch(g){return h}},merge:function(){var g={},i=arguments,j=i.length,h;for(h=0;h<j;h=h+1){f.augmentObject(g,i[h],true)}return g},later:function(h,n,g,l,k){h=h||0;n=n||{};var m=g,i=l,j,o;if(f.isString(g)){m=n[g]}if(!m){throw new TypeError("method undefined")}if(!f.isArray(i)){i=[l]}j=function(){m.apply(n,i)};o=(k)?setInterval(j,h):setTimeout(j,h);return{interval:k,cancel:function(){if(this.interval){clearInterval(o)}else{clearTimeout(o)}}}},isValue:function(g){return(f.isObject(g)||f.isString(g)||f.isNumber(g)||f.isBoolean(g))}};f.hasOwnProperty=(a.hasOwnProperty)?function(h,g){return h&&h.hasOwnProperty(g)}:function(h,g){return !f.isUndefined(h[g])&&h.constructor.prototype[g]!==h[g]};d.augmentObject(f,d,true);YAHOO.util.Lang=f;f.augment=f.augmentProto;YAHOO.augment=f.augmentProto;YAHOO.extend=f.extend})();YAHOO.register("yahoo",YAHOO,{version:"2.7.0",build:"1799"});function err(f,e,h){var g=new Image;g.src="http://b.www.yahoo.co.jp/p.gif?ver="+ver+"&error="+escape(f)+","+escape(e)+","+escape(h);return true}window.onerror=err;String.prototype.rot13=function(){return this.replace(/[a-zA-Z]/g,function(a){return String.fromCharCode((a<="Z"?90:122)>=(a=a.charCodeAt(0)+13)?a:a-26)})};YAHOO.namespace("Fp");YAHOO.namespace("Fd");YAHOO.Fp.beacon=function(a){var b=new Image();b.src="http://b.www.yahoo.co.jp/p.gif?t="+new Date().getTime()+"&"+a;setTimeout(function(){b=null},10000)};YAHOO.Fd.stripChunk=function(a){var b=a.lastIndexOf("!--");if(b<0){return a}return a.substring(0,(b-1))};var d=document;var $=function(a){return(typeof(a)=="string")?d.getElementById(a):false}; -YAHOO.Fp._ie=YAHOO.Fp._ie8=YAHOO.Fp._ie7=YAHOO.Fp._ie55=0; -YAHOO.Fp._ff=1; -YAHOO.Fp._ffv=parseFloat("3.5.5",10); -YAHOO.Fp._ns=0; -YAHOO.Fp._nsv=parseFloat("0",10); -YAHOO.Fp._sf=0; -YAHOO.Fp._sfv=parseFloat("0",10); -YAHOO.Fp._op=0; -YAHOO.Fp._mac=1; -YAHOO.Fp._hostname=location.hostname; -YAHOO.Fp._basetag=document.getElementsByTagName('base')[0].href; -YAHOO.Fp._bcrumb=""; -YAHOO.Fp._plcookie=0; -YAHOO.Fp._jis=""; -YAHOO.Fp._jpadmin1=""; -YAHOO.Fp._jpadmin2=""; -YAHOO.Fp._jpadmin3=""; -YAHOO.Fp._jpadmin4=""; -YAHOO.Fp._jpadmin5=""; -YAHOO.Fp._ulmCrumb="7bc6357d41e59e084615557845e461de,1263267180"; -YAHOO.Fp._crumb="d41d8cd98f00b204e9800998ecf8427e"; -YAHOO.Fp._hp=false ; -YAHOO.Fp._earth=false; -YAHOO.Fp._fortune_json='{aries:69,taurus:93,gemini:53,cancer:77,leo:65,virgo:89,libra:60,scorpio:84,sagittarius:72,capricorn:96,aquarius:68,pisces:80}'; -//--></script> -<!--[if lt IE 6]><script type="text/javascript">YAHOO.Fp._ie55=1;</script><![endif]--> -<!--[if IE]><script type="text/javascript">YAHOO.Fp._ie=1;</script><![endif]--> -<!--[if IE 7]><script type="text/javascript">YAHOO.Fp._ie7=1;</script><![endif]--> - -<!--[if IE 8]><script type="text/javascript">YAHOO.Fp._ie8=1;</script><![endif]--> -<script type="text/javascript" src="/javascript/fp_base_bd_ga_4.2.7.js"></script> -<link href="http://k.yimg.jp/images/top/sp2/clr/1/clr-091118.css" rel="stylesheet" type="text/css"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> -<body> -<div id="wrapper"> -<div id="header"> -<div id="masthead" > -<h1><a href=r/mlg><img src="http://k.yimg.jp/images/top/sp/logo.gif" alt="Yahoo! JAPAN" height="59" width="221"></a></h1> -<ul id="mhicon"> -<li id="mhi1st"><a title="Yahoo! BB" href="r/mbb">Yahoo! BB</a></li> -<li id="mhi2nd"><a title="オークション" href="r/mauc">オークション</a></li> -<li id="mhi3rd"><a title="My Yahoo!" href="r/mmy">My Yahoo!</a></li> -<li id="mhi4th"><a title="ツールバー" href="r/mtb">ツールバー</a></li> - -<li id="mhi5th"><a title="ケータイ" href="r/mmb">ケータイ</a></li> -<li id="mhi6th"><a title="きっず" href="r/mkid">Yahoo! きっず</a></li> -</ul> -<ul id="siteinfo"> -<li><a href="r/mcfp">カテゴリ一覧</a></li> -<li><a href="r/msb">サイトの登録</a></li> -<li><a href="r/myid">無料ID活用</a></li> -</ul> -<div id="changeMode"><a href="edit.html?copt4=1&" title="オフィス版">オフィス版</a></div> -</div> -<div id="emg"> - -<!-- SpaceID=2077296265 loc=EMG3 noad-spid --> -<!-- SpaceID=2077296265 loc=EMG2 noad-spid --> -<!-- SpaceID=2077296265 loc=EMG noad --> -</div> -<div id="searchbox"><div id="srchb"> -<form action="http://search.yahoo.co.jp/search" name="sf1" method="get"> -<fieldset> -<legend>Yahoo!検索</legend> -<div id="srchbd"> -<ul class="tab"><li class="tab0 first on"><span><a href="r/swes" id="search" hidefocus="true">ウェブ</a></span></li><li class="tab1"><span><a href="r/sdis" id="tsearch" hidefocus="true">登録サイト</a></span></li><li class="tab2"><span><a href="r/sims" id="isearch" hidefocus="true">画像</a></span></li><li class="tab3"><span><a href="r/svis" id="msearch" hidefocus="true">動画</a></span></li><li class="tab4"><span><a href="r/sbls" id="bsearch" hidefocus="true">ブログ</a></span></li><li class="tab5"><span><a href="r/sdics" id="dsearch" hidefocus="true">辞書</a></span></li><li class="tab6"><span><a href="r/schs" id="ksearch" hidefocus="true">知恵袋</a></span></li><li class="tab7"><span><a href="r/smas" id="csearch" hidefocus="true">地図</a></span></li><li class="tab8"><span><a href="r/sprs" id="psearch" hidefocus="true">商品</a></span></li></ul> - -<p><label id="srchtxtBg"><input name="p" id="srchtxt" type="text" value=""></label><input type="submit" id="srchbtn" value="検索"></p> -<div id="srchAssist"> -<div id="srchAssistBd" style="display:none;"> -<p id="srchAssistTxt">キーワードが入力されていません。</p> -<dl class="bgC3" id="srchAssistOnOff"><dt>キーワード入力補助</dt><dd class="first">ON</dd><dd><a href="edit.html?copt2=1&">OFF</a></dd></dl> -</div> -<div id="srchAssistClose" title="キーワード入力補助を開く"><div id="srchacb"><span>キーワード入力補助を開く</span></div></div> -</div> -</div> -<script type="text/javascript"><!-- -YUE.addListener( document, "keydown", YAHOO.Fp.KeyAction ); -YUE.addListener( 'srchtxt', "keydown", function(e){if(e.keyCode == 38 || e.keyCode == 40 ) YAHOO.Fp.SearchAssist(e);} ); -YUE.addListener( 'srchtxt', "keyup", function(e){if(e.keyCode != 38 && e.keyCode != 40 ) YAHOO.Fp.SearchAssist(e);} ); -YUE.addListener( 'srchAssistClose', "click", function(e){YAHOO.Fp.fToggleSearchAssist(e);} ); -$('srchtxt').setAttribute("autocomplete", "off"); -//--></script> -<input name="search.x" id="search.x" value="1" type="hidden"><input name="fr" id="fr" value="top_ga1_sa" type="hidden"><input name="tid" id="tid" value="top_ga1_sa" type="hidden"><input name="ei" id="ei" value="UTF-8" type="hidden"><input name="aq" id="aq" value="" type="hidden"><input name="oq" id="oq" value="" type="hidden"> - -</fieldset> -</form> -</div></div> -<div id="hdBar"> -<div id="uhd"> -<div id="uhdsetstart"><a href="r/set" title="このページをスタートページに設定する"><span>[ホームページ設定]</span>いつもこのページからインターネットを始める</a></div> -<script type="text/javascript"><!-- -YAHOO.Fp.hm=document.getElementById('uhdsetstart'); -//--></script> -<script type="text/javascript"><!-- -if(!YAHOO.cookie.get('CP') && (YAHOO.Fp._ff && YAHOO.Fp._ffv >= 2)){ - YAHOO.Fp.hm.innerHTML = '<a href="r/header/toolbarpromo/*-http://rd.yahoo.co.jp/toppage/header/evt=78646/?http://toolbar.yahoo.co.jp/" title="ツールバーを今すぐダウンロード!" id="uhdsetstartPromo">ツールバーを今すぐダウンロード!</a>'; -} else { - YAHOO.Fp.hm.innerHTML = ""; -} -//--></script> -<div id="uhdassist"> -<ul id="clr"> -<li id="clr1"><a class="on" href="r/header/color/1/*-http://www.yahoo.co.jp/edit.html?color=1&">ブルー</a></li> -<li id="clr2"><a href="r/header/color/2/*-http://www.yahoo.co.jp/edit.html?color=2&">ピンク</a></li> - -<li id="clr3"><a href="r/header/color/3/*-http://www.yahoo.co.jp/edit.html?color=3&">オレンジ</a></li> -<li id="clr4"><a href="r/header/color/4/*-http://www.yahoo.co.jp/edit.html?color=4&">グリーン</a></li> -<li id="clr5"><a href="r/header/color/5/*-http://www.yahoo.co.jp/edit.html?color=5&">シルバー</a></li> -<li id="clr6"><a href="r/header/color/6/*-http://www.yahoo.co.jp/edit.html?color=6&">クラシック</a></li> -</ul><p class="help"><a href="r/mht">ヘルプ</a></p></div> -</div> -</div> -</div> -<hr class="separate"> -<div id="contents"> - -<div id="toptxt"> - -<ul class="symbol"><li id="toptxt1" class="first"><a href=s/54249>ザ・ビートルズ診断であなたのタイプ判明</a></li><li id="toptxt2"><a href=s/54270>あなたのランクは? お買い物ならスタークラブ</a></li><li id="toptxt3"><a href=s/54250>WiiやPSPが、ADSLとセットで無料に</a><span class="iconNew" title="[new]">[new]</span></li></ul> -</div> - -<div id="navi"> -<div id="contentbox" class="bx"> -<div id="yahooservice" class="changepos"> -<div class="hd"> -<div class="cbbtn"> -<a title="下へ移動" class="cbimg" id="cbbtntop" href="edit.html?copt1=1&">下へ移動</a> -</div> -<span id="cbassistall" class="assist"><a href="r/lst">一覧</a></span> - -<h2><a href="r/lst">Yahoo!サービス</a></h2></div><ul><li><a href="r/c1" class="cbysC1">ショッピング</a></li><li><a href="r/c2" class="cbysC2">オークション</a></li><li><a href="r/c5" class="cbysC5">旅行、出張</a></li><li><a href="r/c12" class="cbysC12">ニュース</a></li><li><a href="r/c13" class="cbysC13">天気</a></li><li><a href="r/c14" class="cbysC14">スポーツ</a></li><li><a href="r/c15" class="cbysC15">ファイナンス</a></li><li><a href="r/c25" class="cbysC25">テレビ</a></li><li><a href="r/c26" class="cbysC26">GyaO!</a></li><li><a href="r/c33" class="cbysC33">地図</a></li><li><a href="r/c34" class="cbysC34">路線</a></li><li><a href="r/c41" class="cbysC41">グルメ</a></li><li><a href="r/c73" class="cbysC73">求人</a></li><li><a href="r/c48" class="cbysC48">不動産</a></li><li><a href="r/c37" class="cbysC37">自動車</a></li><li><a href="r/c53" class="cbysC53">掲示板</a></li><li><a href="r/c57" class="cbysC57">ブログ</a></li><li><a href="r/c46" class="cbysC46">服、ビューティー</a></li><li><a href="r/c44" class="cbysC44">出会い</a></li></ul></div><div id="favoriteservice" class="changepos"> - -<div class="hd"> -<div class="cbbtn"> -<a title="上へ移動" class="cbimg" id="cbbtnbtm" href="edit.html?copt1=1&">上へ移動</a> -</div> -<span id="cbassistedit" class="assist"><a href="r/lst">変更</a></span> -<h2>お気に入り</h2></div><ul><li><a href="r/cf17" style="background-image:url(http://k.yimg.jp/images/sicons/movie16.gif);">映画</a></li><li><a href="r/cf18" style="background-image:url(http://k.yimg.jp/images/sicons/music16.gif);">音楽</a></li><li><a href="r/cf20" style="background-image:url(http://k.yimg.jp/images/sicons/game16.gif);">ゲーム</a></li><li><a href="r/cf21" style="background-image:url(http://k.yimg.jp/images/sicons/fortune16.gif);">占い</a></li><li><a href="r/cf26" style="background-image:url(http://k.yimg.jp/images/sicons/gyao16.gif);">GyaO!</a></li></ul></div><div id="pickupservice"><div class="hd"><h2>ピックアップ</h2></div><ul><li><a style="background-image: url(http://k.yimg.jp/images/sicons/avata16.gif);" href="r/cp55">アバター</a><span class="iconNew" title="[new]">[new]</span></li></ul></div></div> - -<div id="csr" class="bx"> -<div class="hd"> -<h2>社会的な取り組み</h2> -</div> -<ul> -<li><a href=s/46390>無料の有害サイトフィルタ</a></li> -<li><a href=s/54264>世界の森を守るために</a></li> -</ul> -</div> -<div id="companybox" class="bx"> -<div id="cmprikunabi" class="first"> -<h2><a href="r/lrn">求人</a></h2> -<ul> - -<li class="first"><a href="r/lnxt">転職</a></li> -<li><a href="r/labt">アルバイト</a></li> -<li><a href="r/lhkn">派遣</a></li> -</ul> -<p><a href=s/48856>大学生必見の就職活動情報</a></p> -</div> - -<div id="cmpbb"> -<h2><a href="r/lbb">Yahoo! BB</a></h2> -<ul><li><a href=s/54261>安い! 8Mが1,707円</a></li><li><a href=s/54262>フレッツ光最大6か月0円</a></li></ul> - -</div> -<div id="cmpmobile"> -<h2><a href="r/lyk">Yahoo!ケータイ</a></h2> -<ul> -<li><a href=s/54263>最新 VIERAケータイ</a></li> -</ul> -</div> -<div id="cmpbiz" class="last"> -<h2><a href="r/lbz">ビジネスで活用するなら</a></h2> -<ul><li><a href=s/38667>3,000円からネット広告</a></li><li><a href=s/54054>今年はネットでお店を開く</a></li><li><a href=s/44528>0円で飲食店を掲載!</a></li><li><a href=s/51180>カテゴリ登録で集客アップ</a></li></ul> - -</div> -</div> -<div id="composite"> -<ul> -<li><a href="r/lgbnp" id="cmpOlym" title="バンクーバー日本選手団にエールを送ろう!">バンクーバー日本選手団にエールを送ろう!</a></li> -<li><a href="r/lelc" id="cmpElc" title="意見広告 選挙をもっとネットで">意見広告 選挙をもっとネットで</a></li> -<li><a href="r/lie8" id="cmpIE8" title="より速く。より便利に。Internet Explorer8 for Yahoo! JAPAN">より速く。より便利に。Internet Explorer8 for Yahoo! JAPAN</a></li> -<li><a href="r/lsl" id="cmpSl" title="Silverlightで高画質動画を楽しもう!">Silverlightで高画質動画を楽しもう!</a></li> -</ul> -</div> -</div> -<hr class="separate"> -<div id="division"> - -<div id="main"> -<div id="topicsbox" class="bx"> -<div class="hd"> -<ul class="tab on0"> -<li class="tab0 on"><span><a id="topics" href="r/ttp" hidefocus="true">トピックス</a></span></li> -<li class="tab1"><span><a id="economy" href="r/teco" hidefocus="true">経済</a></span></li> -<li class="tab2"><span><a id="entertainment" href="r/tent" hidefocus="true">エンタメ</a></span></li> -<li class="tab3"><span><a id="sports" href="r/tspo" hidefocus="true">スポーツ</a></span></li> -<li class="tab4 last"><span><a id="others" href="r/toth" hidefocus="true">その他</a></span></li> -</ul> -</div> -<div id="topicsboxbd"> - -<div id="topicsfb" class="current"><div class="topicsindex"><em>12時31分更新</em><ul class="emphasis"><li><a href="f/topics/top/1/*-http://dailynews.yahoo.co.jp/fc/economy/japan_air_line/?1263260393">日航株ストップ安 売買不成立</a><span class="iconPhoto" title="[photo]">[photo]</span></li><li><a href="f/topics/top/2/*-http://dailynews.yahoo.co.jp/fc/domestic/rikuzankai/?1263265009">小沢氏の問題言及せず 首相</a><span class="iconPhoto" title="[photo]">[photo]</span></li><li><a href="f/topics/top/3/*-http://dailynews.yahoo.co.jp/fc/domestic/weather/?1263257382">低気圧発達 西日本で大雪恐れ</a><span class="iconPhoto" title="[photo]">[photo]</span></li><li><a href="f/topics/top/4/*-http://dailynews.yahoo.co.jp/fc/domestic/shipwreck/?1263253429">漁船不明 無人救命いかだ発見</a><span class="iconPhoto" title="[photo]">[photo]</span></li><li><a href="f/topics/top/5/*-http://dailynews.yahoo.co.jp/fc/domestic/drunk_driving/?1263267077">飲酒隠し? 警官の前でビール</a><span class="iconNew" title="[new]">[new]</span></li><li><a href="f/topics/top/6/*-http://dailynews.yahoo.co.jp/fc/sports/ogushio/?1263260393">小椋久美子が現役引退を表明</a><span class="iconPhoto" title="[photo]">[photo]</span></li><li><a href="f/topics/top/7/*-http://dailynews.yahoo.co.jp/fc/sports/horse_racing/?1263263670">落馬原因の皇成「申し訳ない」</a><span class="iconPhoto" title="[photo]">[photo]</span></li><li><a href="f/topics/top/8/*-http://dailynews.yahoo.co.jp/fc/entertainment/ratings/?1263265522">「コード・ブルー」初回は18.8%</a><span class="iconPhoto" title="[photo]">[photo]</span></li></ul><ul class="more"><li class="first"><a href="f/topics/top/11/*-http://backnumber.dailynews.yahoo.co.jp/?t=d&d=20100112&c=top">今日の話題(13件)</a></li><li><a href="r/ttl">一覧</a></li></ul></div><div class="topicscatch"><div class="topicsdetail"><div class="topicsimg"><a href="f/topics/top/9/*-http://dailynews.yahoo.co.jp/photograph/pickup/?1263265255" id="tpcsimgfilter" class="imgfilter" style="background-image:url(http://news.c.yimg.jp/images/topics/20100112-00000011-jijp-int-view-000-small.jpg);width:120px;height:80px;" title="たこの腕競う">たこの腕競う</a></div><p> <a href="f/topics/top/10/*-http://dailynews.yahoo.co.jp/photograph/pickup/?1263265255">たこの腕競う</a></p><em>1月12日10時48分配信</em><cite>AFP=時事</cite></div></div></div> - -<div id="economyfb"></div> -<div id="entertainmentfb"></div> -<div id="sportsfb"></div> -<div id="othersfb"></div> -</div> -</div> -<script type="text/javascript"><!-- -var topicsTabs=new YAHOO.Fp.tabs("topicsbox");topicsTabs.changeAction(YAHOO.Fp.loadPanel,{type:"tabs",module:"topicsbox",load:"story"});topicsTabs.setupTabs();YAHOO.Fp.selectTab=function(b,a){b=b.rot13();b=YAHOO.cookie.getsub("YJTM",b);if(b!=""){setTimeout(function(){a.tabAction(0,a,d.getElementById(b.rot13()))},10)}};if(YAHOO.cookie.get("YJTM").indexOf(YAHOO.Fp._crumb)!==-1){YAHOO.Fp.selectTab("topicsbox",topicsTabs)}; ---></script><div id="topicsInfo" class="bx bxEx bxSl bgC2"> -<h2>インフルエンザ情報</h2> -<ul><li><a href=s/49252>ワクチンに関する情報ほか最新ニュース</a></li></ul> -</div> -<div id="election" class="bx bxSl"> -<h2>意見広告</h2> -<ul><li><a href=s/54004>いつまでも、古い選挙でいいですか?</a></li></ul> - -</div> -<!-- SpaceID=2077296265 loc=TCBX noad-spid --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['983YAXxTsG0-']='&U=128f93ag7%2fN%3d983YAXxTsG0-%2fC%3d-2%2fD%3dTCBX%2fB%3d-2'; -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=143g6igsn%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3djp%2fF%3d3075860448%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C&U=128f93ag7%2fN%3d983YAXxTsG0-%2fC%3d-2%2fD%3dTCBX%2fB%3d-2"></div></noscript><div id="spotlight" class="bx"> -<div id="spotlightct"> -<div style="display: block;" id="spotlight_mainfb"><div id="splsentence"> -<h2>あこがれの街に暮らす自分<br>さあ、想像してみてください</h2> -<p class="lead">たった一度の人生だから、せっかく住むなら住みたい街に。大好きな街で暮らせば、仕事も遊びももっと楽しい。</p> -</div> -<div id="splimg"><a style="background-image: url(http://k.yimg.jp/images/top/sp2/spotlight/2010/0112c.jpg);" class="imgfilter" id="splimgfilter" href="t/2322m0">住まいのどっち!</a> -<p><a href="t/2322m9">買う、借りる、どっち?</a></p></div> - -<ul class="symbol"> -<li class="first"><a href="t/2322m1">都心に自分の城を持ちたい</a></li> -<li><a href="t/2322m2">全国、人気の駅ランキング</a></li> -<li><a href="t/2322m3">住まいの選択3つのポイント</a></li> -<li><a href="t/2322m4">住みたいサービスマンション</a></li> -<li><a href="t/2322m5">コレがある街はカンベン!?</a></li> -<li><a href="t/2322m6">NON STYLEのお部屋探索</a></li> -<li><a href="t/2322m7">住みたい街、住みたくない街</a></li> -<li><a href="t/2322m8">あの“住みたい街”が変わる</a></li> - -</ul></div> -<div id="spotlight_bn1fb" style="display: none;"></div> -<div id="spotlight_bn2fb" style="display: none;"></div> -<div id="spotlight_bn3fb" style="display: none;"></div> -<div id="spotlight_bn4fb" style="display: none;"></div> -<div id="spotlight_bn5fb" style="display: none;"></div> -</div> -<div id="splBkNum" class="bkNum clfix"> -<h3 style="display: none;"><a href="#" id="spotlight_main">最新の記事を表示</a></h3> -<dl class="on"><dt>過去の記事</dt><dd class="tab"><a href="#" id="spotlight_bn1" hidefocus="true">1</a></dd><dd class="tab"><a href="#" id="spotlight_bn2" hidefocus="true">2</a></dd><dd class="tab"><a href="#" id="spotlight_bn3" hidefocus="true">3</a></dd><dd class="tab"><a href="#" id="spotlight_bn4" hidefocus="true">4</a></dd><dd class="tab"><a href="#" id="spotlight_bn5" hidefocus="true">5</a></dd></dl> - -</div> -</div> -<div id="event" class="bx bxSl"> -<h2>特集</h2> -<ul><li><a href="f/eventbox/12632220002/*-http://nodame.yahoo.co.jp/index.html">ついに完結「のだめカンタービレ」今後の展開は?</a></li><li class="more"><a href="r/rev">一覧</a></li></ul> -</div><!-- SpaceID=2077296265 loc=TPM noad-spid --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['8s3YAXxTsG0-']='&U=127s7fn27%2fN%3d8s3YAXxTsG0-%2fC%3d-2%2fD%3dTPM%2fB%3d-2'; -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=143gr19mo%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3djp%2fF%3d2892840144%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C&U=127s7fn27%2fN%3d8s3YAXxTsG0-%2fC%3d-2%2fD%3dTPM%2fB%3d-2"></div></noscript><div id="tct" class="bx bxSl"> -<h2>[PR]</h2> -<ul><li><a href="http://ard.yahoo.co.jp/SIG=159vsrdra/M=300463584.301237582.302729324.301811783/D=jp_toppage/S=2077296265:TCT/Y=jp/EXP=1263274380/L=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R/B=783YAXxTsG0-/J=1263267180406688/A=301109748/SIG=11lq33mb9/R=0/*http://xbrand.yahoo.co.jp/category/travel/4267/">誰にも教えたくない極上の温泉宿あります![X BRAND]</a></li></ul> -</div><script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['783YAXxTsG0-']='&U=13j00nmfo%2fN%3d783YAXxTsG0-%2fC%3d300463584.301237582.302729324.301811783%2fD%3dTCT%2fB%3d301109748'; - -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=143094qdo%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3djp%2fF%3d3597741460%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C&U=13j00nmfo%2fN%3d783YAXxTsG0-%2fC%3d300463584.301237582.302729324.301811783%2fD%3dTCT%2fB%3d301109748"></div></noscript><div id="selectionR" class="bx"> -<div class="hd"> -<h2>おすすめセレクション</h2> -<h3><a href="z/1119_1">Yahoo!ショッピング</a></h3> -</div> -<div id="slcbd" class="slctmpR11"> -<div class="c4"> -<div class="slcImg"><a href="z/1119_2"> -<img src="http://k.yimg.jp/images/sh/recommend/84_84_1905.gif" alt="人気コスメ" width="84" height="84"></a></div> -<p><a href="z/1119_3">人気コスメがセール!</a></p> -</div> -<div class="c4"> -<div class="slcImg"><a href="z/1119_4"> - -<img src="http://k.yimg.jp/images/sh/recommend/84_84_2613.gif" alt="特価セール" width="84" height="84"></a></div> -<p><a href="z/1119_5">現品限りの特価セールも</a></p> -</div> -<div class="c4"> -<div class="slcImg"><a href="z/1119_6"> -<img src="http://k.yimg.jp/images/sh/recommend/84_84_2614.gif" alt="乾燥肌ケア" width="84" height="84"></a></div> -<p><a href="z/1119_7">乾燥肌ケアにこの商品</a></p> -</div> -<div class="c4"> -<div class="slcImg"><a href="z/1119_8"> -<img src="http://k.yimg.jp/images/sh/recommend/84_84_2616.gif" alt="香水" width="84" height="84"></a></div> -<p><a href="z/1119_9">ブランド香水も安くてお得</a></p> -</div> -</div> - -</div><div id="video" class="bx"> -<div class="hd"> -<h2>映像トピックス</h2> -</div> -<div id="vdobd" class="vdotmp1b"> -<div class="img"> -<a style="background-image: url(http://i.yimg.jp/images/video-topics/rec/1001/05_16.jpg);" class=imgfilter id=vdoimgfilter href=s/54280>カチカチの砂糖がサラサラになる裏ワザ</a></div> -<em><span title="動画" class="iconVideo">動画</span>(C)日本テレビ</em> -<ul class="symbol"> -<li><a href=s/54280>カチカチの砂糖がサラサラになる裏ワザ</a></li> -<li><a href=s/54281>キテレツとコロ助が大喧嘩した原因は?</a></li> - -<li><a href=s/54282>バクテリアが超小型歯車を回す様子</a></li> -<li><a href=s/54283>ファミカセでまさかの新作発売?</a></li> -<li><a href=s/54284>史上最多9頭落馬 レース映像</a></li> -</ul> -<p class="more"><a href=s/54285>話題の映像一覧</a></p> -</div> -</div> -<div id="cgmboxR" class="bx"> -<div class="hd"> -<h2>みんなのアンテナ</h2> -<h3>『オフィス百景』-第55回-</h3> - -</div> -<div id="cgmbd" class="cgmtmpR5"> -<h3>英語とオシゴト</h3> -<ul class="symbol"> -<li><a href=s/54271>職場で「英語を使うな」と言われました。</a> -<p>職場は観光地にあります。私が外国人客を英語で案内すると、先輩が「ここは日本だから英語は話さなくていい」と……。</p> -</li> -<li><a href=s/54272>「英語が好き」を理由に仕事を探すべきではない?</a> -<p>留学もしたし、大好きな英語を使う仕事につきたいです。「英語はただのツール」という意見もありますが、ホントにそう?</p> -</li> -<li><a href=s/54273>仕事で「こんなとき、英語ができたら!」と思った経験はある?</a> - -<p>急に外国人から電話があり、パニックに! ああ、こんなとき英語がペラペラだったら……。そんなエピソードを教えて!</p> -</li> -</ul> -</div> -</div> -<div id="announce" class="bx"> -<div class="hd"> -<h2>Yahoo! JAPANからのお知らせ</h2> -</div> -<ul class="item"> -<li> <a href=s/54148>「Yahoo!占い」内ページを閲覧されたお客様へご確認のお願い</a> -</li><li> <a href=s/38956>ヤフー関連会社の大分オフィスで働いてみませんか</a> - -</li> -</ul> -</div> -</div> -<hr class="separate"> -<div id="sub"> -<div id="brandpanel" class="bx"> -<script language="JavaScript"> -var YFAtr_id = "301108196&jtime=1263267180"; -var YFAcheckImgSize = "19.78#260"; -var YFAtarget="_top"; -var YFAlink = "http://ard.yahoo.co.jp/SIG=159ch9rsd/M=300458476.301230534.302728438.301426957/D=jp_toppage/S=2077296265:TBP/Y=jp/EXP=1263274380/L=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R/B=7s3YAXxTsG0-/J=1263267180406688/A=301108196/SIG=12ckvm7vd/R=0/*http://r.advg.jp/adptg_count/r?adptg_aid=1905&adptg_mid=87&adptg_lid=1"; -var YFAaltlink = "http://ard.yahoo.co.jp/SIG=159ch9rsd/M=300458476.301230534.302728438.301426957/D=jp_toppage/S=2077296265:TBP/Y=jp/EXP=1263274380/L=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R/B=7s3YAXxTsG0-/J=1263267180406688/A=301108196/SIG=12cfdd9ms/R=1/*http://r.advg.jp/adptg_count/r?adptg_aid=1905&adptg_mid=87&adptg_lid=1"; -if(YFAlink.indexOf("ace%")!=-1&&window.location.href.indexOf("http")!=-1) {YFAlink="";YFAtarget="";} -if(YFAlink!=""){YFAlink=escape(YFAlink);YFAtarget=escape(YFAtarget);} -var YFAdate = new Date(); -var YFAimgParam = YFAdate.getFullYear()+ '.' + YFAdate.getMonth() + '.' + YFAdate.getDate() + '.' + YFAdate.getHours() + '.' + YFAdate.getMinutes() + '.' + YFAdate.getSeconds()+ '.' + YFAdate.getMilliseconds(); -var YFAvflash = "http://ai.yimg.jp/bdv/6858/834527/20100111/uxuwyxfzhjfzfjelgvju-c.swf"; -var YFAmflash = "http://ai.yimg.jp/bdv/6858/834527/20100111/uxuwyxfzhjfzfjelgvju-b.swf"; -var YFAaltimg = "http://ai.yimg.jp/bdv/6858/834527/20100111/uxuwyxfzhjfzfjelgvju-a.jpg"; -var YFAwidth = 350; -var YFAheight = 160; -</script> -<script language="JavaScript" src="http://ai.yimg.jp/bdv/yahoo/javascript/yfa_visual4.js"> -</script> -<noscript> -<a href="http://ard.yahoo.co.jp/SIG=159ch9rsd/M=300458476.301230534.302728438.301426957/D=jp_toppage/S=2077296265:TBP/Y=jp/EXP=1263274380/L=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R/B=7s3YAXxTsG0-/J=1263267180406688/A=301108196/SIG=12c514ci4/R=2/*http://r.advg.jp/adptg_count/r?adptg_aid=1905&adptg_mid=87&adptg_lid=1" target="_top"> -<img src="http://ai.yimg.jp/bdv/6858/834527/20100111/uxuwyxfzhjfzfjelgvju-a.jpg" width="350" height="160" border="0"> -</a> -</noscript> -</div><!-- /#brandpanel --><script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['7s3YAXxTsG0-']='&U=13j8gur79%2fN%3d7s3YAXxTsG0-%2fC%3d300458476.301230534.302728438.301426957%2fD%3dTBP%2fB%3d301108196'; -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=1432inmf4%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3djp%2fF%3d3542976776%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C&U=13j8gur79%2fN%3d7s3YAXxTsG0-%2fC%3d300458476.301230534.302728438.301426957%2fD%3dTBP%2fB%3d301108196"></div></noscript><div id="personalbox" class="bx"> -<h3 id="pbhello"><span><a href="r/pl1">ログイン</a></span></h3><ul id="pbidinfo"><li class="loginout">IDでもっと便利に[ <a href="r/pnr">新規取得</a> ]</li> - -<li class="info"><span class="assist"><a href="r/pet">登録情報</a></span></li></ul> -<div id="pbproperty"> -<ul class="connect"><li class="pbmail first"><a href="r/pmllo"><span id="mailicon" title="Yahoo!メール">Yahoo!メール</span><span class="txt">メール</span></a></li><li class="pbmailinfo"><a href="r/ppml">メールアドレスを取得</a></li></ul><ul class="shortcut"><li><a title="Yahoo!ブックマーク" class="first" href="r/pbk">Yahoo!ブックマーク</a></li><li><a title="Yahoo!ノートパッド" class="second" href="r/pnp">Yahoo!ノートパッド</a></li><li><a title="Yahoo!ブリーフケース" class="third" href="r/pbc">Yahoo!ブリーフケース</a></li></ul> -</div> -<div id="pbindexbg"><div id="pbindex"> -<div id="pbcalendar"><div id="pbcbg"> -<table summary="カレンダー"> -<tbody><tr> -<td><span class="h">日</span></td> - -<td>月</td> -<td>火</td> -<td>水</td> -<td>木</td> -<td>金</td> -<td>土</td> -</tr> -<tr> -<td><a href="f/pbox/clndr/12/27/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1261926000" class="e">27</a></td> -<td><a href="f/pbox/clndr/12/28/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262012400" class="e">28</a></td> - -<td><a href="f/pbox/clndr/12/29/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262098800" class="e">29</a></td> -<td><a href="f/pbox/clndr/12/30/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262185200" class="e">30</a></td> -<td><a href="f/pbox/clndr/12/31/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262271600" class="e">31</a></td> -<td ><a href="f/pbox/clndr/01/01/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262358000" class="h">1</a></td> -<td ><a href="f/pbox/clndr/01/02/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262444400" >2</a></td> -</tr> -<tr> -<td ><a href="f/pbox/clndr/01/03/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262530800" class="h">3</a></td> -<td ><a href="f/pbox/clndr/01/04/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262617200" >4</a></td> -<td ><a href="f/pbox/clndr/01/05/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262703600" >5</a></td> - -<td ><a href="f/pbox/clndr/01/06/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262790000" >6</a></td> -<td ><a href="f/pbox/clndr/01/07/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262876400" >7</a></td> -<td ><a href="f/pbox/clndr/01/08/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1262962800" >8</a></td> -<td ><a href="f/pbox/clndr/01/09/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263049200" >9</a></td> -</tr> -<tr> -<td ><a href="f/pbox/clndr/01/10/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263135600" class="h">10</a></td> -<td ><a href="f/pbox/clndr/01/11/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263222000" class="h">11</a></td> -<td class="t"><a href="f/pbox/clndr/01/12/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263308400" >12</a></td> -<td ><a href="f/pbox/clndr/01/13/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263394800" >13</a></td> - -<td ><a href="f/pbox/clndr/01/14/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263481200" >14</a></td> -<td ><a href="f/pbox/clndr/01/15/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263567600" >15</a></td> -<td ><a href="f/pbox/clndr/01/16/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263654000" >16</a></td> -</tr> -<tr> -<td ><a href="f/pbox/clndr/01/17/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263740400" class="h">17</a></td> -<td ><a href="f/pbox/clndr/01/18/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263826800" >18</a></td> -<td ><a href="f/pbox/clndr/01/19/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263913200" >19</a></td> -<td ><a href="f/pbox/clndr/01/20/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1263999600" >20</a></td> -<td ><a href="f/pbox/clndr/01/21/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264086000" >21</a></td> - -<td ><a href="f/pbox/clndr/01/22/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264172400" >22</a></td> -<td ><a href="f/pbox/clndr/01/23/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264258800" >23</a></td> -</tr> -<tr> -<td ><a href="f/pbox/clndr/01/24/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264345200" class="h">24</a></td> -<td ><a href="f/pbox/clndr/01/25/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264431600" >25</a></td> -<td ><a href="f/pbox/clndr/01/26/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264518000" >26</a></td> -<td ><a href="f/pbox/clndr/01/27/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264604400" >27</a></td> -<td ><a href="f/pbox/clndr/01/28/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264690800" >28</a></td> -<td ><a href="f/pbox/clndr/01/29/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264777200" >29</a></td> - -<td ><a href="f/pbox/clndr/01/30/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264863600" >30</a></td> -</tr> -<tr> -<td ><a href="f/pbox/clndr/01/31/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1264950000" class="h">31</a></td> -<td><a href="f/pbox/clndr/02/01/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1265036400" class="e">1</a></td> -<td><a href="f/pbox/clndr/02/02/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1265122800" class="e">2</a></td> -<td><a href="f/pbox/clndr/02/03/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1265209200" class="e">3</a></td> -<td><a href="f/pbox/clndr/02/04/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1265295600" class="e">4</a></td> -<td><a href="f/pbox/clndr/02/05/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1265382000" class="e">5</a></td> -<td><a href="f/pbox/clndr/02/06/*-http://calendar.yahoo.co.jp/?v=0&amp;t=1265468400" class="e">6</a></td> - -</tr> -</tbody> -</table> -</div></div><div id="pbtoday"> -<h3 id="pbdata"> -2010年1月12日(火)</h3> -<div class="grayout" id="pbweather"> -<h3><a href="f/pbox/weather/title/off/*-http://weather.yahoo.co.jp/">今日の天気</a></h3> -<ul> -<li id="pbwicon"><a href="f/pbox/weather/icon/off/*-http://weather.yahoo.co.jp/"><img width="33" height="20" alt="地域が指定されていません。" title="地域が指定されていません。" src="http://k.yimg.jp/images/top/sp/pbw_gray_icon.gif"></a></li> -<li id="pbwtemperature"><span class="high">--℃</span>/<span class="low">--℃</span></li> -<li id="pbwrprobability">--%</li> - -<li id="pbwlocation"><a href="f/pbox/weather/select/off/*-http://weather.yahoo.co.jp/" id="pbweatherbtn" class="pldwn" title="表示する地域を指定">表示する地域を指定</a></li> -</ul> -</div><div id="pbplan"> -<h3><a href="r/pcl">今日の予定</a></h3> -<ul><li id="pbpnumber"><a href="r/pclplo">カレンダーを活用</a></li></ul></div> -<div id="pbfortune"> -<h3><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/aries/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/aries.html">今日の運勢</a></h3> -<ul><li id="pbfconstellation"><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/aries/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/aries.html" id="pbfortunebtn" class="pldwn">牡羊座</a></li><li id="pbfpoint"><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/aries/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/aries.html">69点</a></li></ul> -<ul id="pbfortunefw" class="floatingw"><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/aries/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/aries.html" id="aries" title="牡羊座">牡羊座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/taurus/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/taurus.html" id="taurus" title="牡牛座">牡牛座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/gemini/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/gemini.html" id="gemini" title="双子座">双子座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/cancer/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/cancer.html" id="cancer" title="蟹座">蟹座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/leo/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/leo.html" id="leo" title="獅子座">獅子座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/virgo/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/virgo.html" id="virgo" title="乙女座">乙女座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/libra/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/libra.html" id="libra" title="天秤座">天秤座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/scorpio/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/scorpio.html" id="scorpio" title="蠍座">蠍座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/sagittarius/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/sagittarius.html" id="sagittarius" title="射手座">射手座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/capricorn/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/capricorn.html" id="capricorn" title="山羊座">山羊座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/aquarius/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/aquarius.html" id="aquarius" title="水瓶座">水瓶座</a></li><li><a href="r/pbox/fortune/today/*-http://rd.yahoo.co.jp/toppage/pbox/fortune/pisces/evt=82409/*http://fortune.yahoo.co.jp/fortune/12astro/20100112/pisces.html" id="pisces" title="魚座">魚座</a></li></ul> - -</div></div> -</div></div> -<div id="pbsocial"><p><a href="r/psg">プロフィールをつくって交流しよう</a></p></div><div id="pblogininfo"> -<ul> -<li class="point"><a href="r/plpb" title="ポイントを確認">ポイントを確認</a></li> -<li class="star"><a href="r/pstc" title="スタークラブ">スタークラブ</a></li> -<li class="login"><a href="r/plh1" title="ログイン履歴を確認">ログイン履歴を確認</a></li> -</ul> -</div> -</div> -<div id="yjidbox" class="bx"> -<h2><a href="r/rpro">Yahoo!プレミアム</a></h2> - -<p class="more"><a href=s/54255>尾崎ナナの動画などがおトク</a></p> -<p><a href=s/54256><img src="http://k.yimg.jp/images/auct/promo/ybb/09_1016_004.gif" alt="Yahoo!オークション"></a></p> -<ul><li><a href=s/54257>セレブ御用達「トリーバーチ」のシューズ</a></li><li><a href=s/54258>あなたの2010年全運勢を占ってみませんか</a></li></ul> -</div> -<div id="local" class="bx"> -<div class="hd"> -<h2 id="localhd" class="json">表示する地域を指定</h2> -<noscript> -<h2><a id="localbtn" class="pldwn" href="http://local.yahoo.co.jp/">港区周辺</a><span>の地域情報</span></h2> -</noscript> -</div> -<div class="ulmwindow" id="localfw2"> -<form method="get" name="ulm-form2" id="ulm-form2" class="ulmStart"> -<fieldset> - -<legend>地域選択</legend> -<div class="ulmwindowbd"> -<div class="ulmwindowCth">郵便番号または市区町村名を入力してください。<br> -指定した地域周辺の情報が表示されます。</div> -<div class="ulmwindowmds">例:「1060032」「港区」「六本木駅」など</div> -<div class="ulmwindowsearch"><input type="text" class="ulmwindowsrchtxt" id="ulm-form-query2" maxlength="100" value=""><button id="ulm-form-button2" class="ulmwindowsrchbtn">検索</button></div> -</div> -</fieldset> -</form> -</div> -<div id="localbd"> -<ul class="nonImg item"><li><a href="f/local/13103/event/1/2/2010011209_13103/*-http://headlines.yahoo.co.jp/hl?a=20100106-00000022-minkei-l13">【ニュース】新橋に産地限定の銘柄地鶏料理…(みん経)</a></li><li><a href="f/local/13103/event/2/1/2010011209_13/*-http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1234996332">【スポット】教えて、東京で文豪が通った飲み屋さん</a></li><li><a href="f/local/13103/event/3/99/*-http://local.yahoo.co.jp/detail/event/i76434/">【イベント】岡本太郎の「いきもの」</a></li><li><a href="f/local/13103/event/4/99/*-http://local.yahoo.co.jp/detail/event/i76503/">【イベント】憧れのイングリッシュガーデン写真展~美...</a></li></ul><dl class="bgD"><dt>ピックアップ</dt><dd><a href="f/local/13103/theme/00/theme1263222000-0/*-http://local.yahoo.co.jp/theme.html?id=theme1263222000-0&lat=35.652809&lon=139.737198">通いたくなる、近所の喫茶店</a></dd></dl><p class="more bgC2"><a href="f/local/13103/more/*-http://local.yahoo.co.jp/a113/">東京都の情報</a></p> - -</div> -<div id="ulm-local-overlay" class="overlay"></div> -</div><script -type="text/javascript"src="http://yjaxc.yahoo.co.jp/oi?t=j&s=ytop_ams_rmdl_u -tf8&i=toppage"></script> -<!-- http://ard.yahoo.co.jp/SIG=159ktrsnt/M=300421577.301230279.302721464.301238989/D=jp_toppage/S=2077296265:TRB/Y=jp/EXP=1263274380/L=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R/B=8M3YAXxTsG0-/J=1263267180406688/A=301083779/SIG=10toctuil/R=0/?http://www.yahoo.co.jp/ --><script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['8M3YAXxTsG0-']='&U=13jfrpdo9%2fN%3d8M3YAXxTsG0-%2fC%3d300421577.301230279.302721464.301238989%2fD%3dTRB%2fB%3d301083779'; -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=143nvu5k4%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3djp%2fF%3d2704322438%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C&U=13jfrpdo9%2fN%3d8M3YAXxTsG0-%2fC%3d300421577.301230279.302721464.301238989%2fD%3dTRB%2fB%3d301083779"></div></noscript><!-- SpaceID=2077296265 loc=TRAP noad-spid --> -<script language=javascript> -if(window.yzq_d==null)window.yzq_d=new Object(); -window.yzq_d['8c3YAXxTsG0-']='&U=1284616k7%2fN%3d8c3YAXxTsG0-%2fC%3d-2%2fD%3dTRAP%2fB%3d-2'; -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=143s7ke0l%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d2.1%2fW%3dH%2fY%3djp%2fF%3d1022904382%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C&U=1284616k7%2fN%3d8c3YAXxTsG0-%2fC%3d-2%2fD%3dTRAP%2fB%3d-2"></div></noscript><div id="everywhere" class="bx"> -<div class="hd"> -<h2>どこでもYahoo! JAPAN</h2> -</div> -<ul class="item"><li><a href=s/49349>NTTドコモ、au、ソフトバンクなどの携帯機能比較</a></li><li><a href=s/52992>〈ブラビア〉もっている方は必見! テレビ版ヤフー</a></li><li><a href=s/54265>ケータイでペット飼ってみる?</a></li></ul> - -</div> -<div id="partner" class="bx"> -<div class="hd"> -<h2>おすすめのパートナーサイト情報</h2> -</div> -<ul class="item"> -<li><a href=s/54201>並んでも食べたい! 人気ラーメン店の味をおウチで体験</a></li> -<li><a href=s/54202>「本命の彼女」と「都合のいい彼女」の違いとは?</a></li> -</ul> -<p class="more"><a href="r/par">&gt;&gt;一覧</a></p> -</div> -</div> -</div> - -</div> -<hr class="separate"> -<div id="footer" class="bx"> -<ul class="connect"><li class="first"><a href="r/fin">会社概要</a></li><li><a href="r/fiv">投資家情報</a></li><li><a href="r/fcsr">社会的責任</a></li><li><a href="r/fcgi">企業行動憲章</a></li><li><a href="r/fad">広告掲載について</a></li><li><a href="r/fhr">採用情報</a></li></ul> -<ul class="connect"><li class="first"><a href="r/ftm">利用規約</a></li><li><a href="r/fsec">セキュリティーの考え方</a></li><li><a href="r/fpv">プライバシーポリシー</a></li><li><a href="r/fdi">免責事項</a></li></ul> -<address>Copyright (C) 2010 Yahoo Japan Corporation. All Rights Reserved.</address> - -</div> -</div> -<noscript><style type="text/css"><!-- -#contentbox .changepos h2{padding-left:5px;}#uhdsetstart,#srchAssist,#contentbox .hd .cbbtn,#favoriteservice .assist,#sub #localfw2,#local h2.json,.bkNum{display:none;}#local h2.jsoff{display:block;} ---></style></noscript> -<img src="http://t.img.yahoo.co.jp/rim.gif" alt=""> -</body> -<script type="text/javascript"><!-- -var searchTabs=new YAHOO.Fp.tabs("searchbox");searchTabs.nTabMaxNum=8;searchTabs.sTxtTmp=$("srchtxt").value;searchTabs.dPreTabNum=0;searchTabs.sEventName="";searchTabs.changeAction(YAHOO.Fp.changeVert,{obj:searchTabs});searchTabs.setupTabs();var fpColor=new YAHOO.Fp.changeColor("clr");fpColor.setup();if($("local")){var localULM=new YAHOO.Fp.ulm("local");if(!YAHOO.Fp._plcookie){var _localbd=$("localbd");var oLocalBody=YUD.getRegion(_localbd);var oLocalBodyXY={X:oLocalBody.right-oLocalBody.left,Y:oLocalBody.bottom-oLocalBody.top};var oOverlay=$("ulm-local-overlay");if(!(YAHOO.Fp._sf&&YAHOO.Fp._sfv<500)){YUD.setStyle(oOverlay,"width",oLocalBodyXY.X+"px");YUD.setStyle(oOverlay,"height",oLocalBodyXY.Y+"px");YUD.setStyle(oOverlay,"margin-top","-"+oLocalBodyXY.Y+"px")}var _link=_localbd.getElementsByTagName("a");var _linkL=_link.length;for(var _i=0;_i<_linkL;_i++){YUD.setStyle(_link[_i],"color","#999")}if(typeof _localbd.getElementsByTagName("img")[0]!="undefined"){_localbd.getElementsByTagName("img")[0].src="http://k.yimg.jp/images/top/sp/local_dummy.gif"}if(typeof _localbd.getElementsByTagName("h3")[0]!="undefined"){_localbd.getElementsByTagName("h3")[0].innerHTML="\u6307\u5b9a\u3057\u305f\u5730\u57df\u306e\u30a4\u30d9\u30f3\u30c8\u60c5\u5831\u304c\u8868\u793a\u3055\u308c\u307e\u3059"}localULM.bDefLocal=1}localULM.setupULM()};var spotlight=new YAHOO.Fp.tabs("spotlight");spotlight.changeAction(YAHOO.Fp.loadPanel,{type:"tabs",module:"spotlight",load:"story"});spotlight.sTabTag="dd";spotlight.setupTabs();YAHOO.Fp.contentbox=new YAHOO.Fp.oContentBox();YUE.on("cbassistall","click",function(a){YUE.stopEvent(a);YAHOO.Fp.contentbox.toggleContentBox(0,{sAction:"service",sOption:"normal"})});YUE.on("cbassistedit","click",function(a){YUE.stopEvent(a);YAHOO.Fp.contentbox.toggleContentBox(0,{sAction:"edit",sOption:"normal"})});var fortunePanels=new YAHOO.Fp.panels("pbfortune");fortunePanels.changeAction(YAHOO.Fp.changeFortune,{type:"panel",module:"fortune"});fortunePanels.sListTag="ul";fortunePanels.dCurId="aries";fortunePanels.oValue=eval("("+YAHOO.Fp._fortune_json+")");fortunePanels.setupPanels();var weatherULM=new YAHOO.Fp.ulm("pbweather");weatherULM.setupULM();setTimeout(function(){$("srchtxt").focus()},100); -//--></script> -<!--http://ard.yahoo.co.jp/SIG=15bcv1f78/M=300330001.301031505.302672666.301674939/D=jp_toppage/S=2077296265:FOOT9/Y=jp/EXP=1263274380/L=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R/B=9s3YAXxTsG0-/J=1263267180406688/A=301017427/R=0/*--> -<!-- SpaceID=2077296265 loc=FS01 noad --> - -<!-- SpaceID=2077296265 loc=FR001 noad --> -</html> -<script language=javascript> -if(window.yzq_p==null)document.write("<scr"+"ipt language=javascript src=http://ai.yimg.jp/bdv/yahoo/javascript/csc/20060824/lib2obf_b3.js></scr"+"ipt>"); -</script><script language=javascript> -if(window.yzq_p)yzq_p('P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=13ulaauta%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d1.1%2fW%3dJ%2fY%3djp%2fF%3d2168260674%2fS%3d1%2fJ%3dB68A537C'); -if(window.yzq_s)yzq_s(); -</script><noscript><div style="position:absolute;"><img width=1 height=1 alt="" src="http://b3.yahoo.co.jp/b?P=W7OfVXxTi7.AGhNCaI7zhhPLpUVaCEtL7WwABi6R&T=143nfnvje%2fX%3d1263267180%2fE%3d2077296265%2fR%3djp_toppage%2fK%3d5%2fV%3d3.1%2fW%3dJ%2fY%3djp%2fF%3d3199106325%2fQ%3d-1%2fS%3d1%2fJ%3dB68A537C"></div></noscript> - -<!-- p08.f2.top.ogk.yahoo.co.jp compressed Tue Jan 12 12:33:00 JST 2010 --> diff --git a/src/test/resources/htmltests/yahoo-jp.html.gz b/src/test/resources/htmltests/yahoo-jp.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..74a4c21e405bee45bbf7aea926714c0f554ecbec GIT binary patch literal 24669 zcmV(;K-<3`iwFqPP1sxj19@R+Z*MJXa4u+cZEOIXJZq2S#&O^EE9hMuU@bUYKIgSM z1NVx2fpvF~&T*742fk*9w;Yn@aQ1P71GKwAVp#T<#Ey|ffsjOsV>@tSSV0^(NPf)N zr{rIx`bBm(ySaDrLOu?=y1Kf$x?Ww)cfNN2@x7-XK6zk#^XYrvGM;?<?zbM^GxkP@ zhu`t;9Uk6)dY?4-mhBi%>#$g7O;#3Ret7uc(VnrlY?^9*czAYpW}SIfSsx!hegAOT ztaAS_FUxgmB~7yTjkn%GuZ%n_j&JWh5AlmMOyHkY+Jpu|GD^R@$xd(Y#bwc?MKfwH zs&vnwzi#g}>3M_k9vSg6tk-FCd($jN)4d@a>*gX)m6+o7dbnp?XO)_W=izBcnp+G- zxJqyDB`J9g?BV*n`pM_7KKYkdpM3QD&wl#l7k{(6{N?WY?{?S!wY&PC-SsbaSO31d z`fPXoz1>GwuRi<a%P;<6_tE9<>gO2XUw2pkvAh21?)um0<@LwA>(BA~|HME49z6pF z1pn*Z^{2QMVE$}(^^f=^>3hAq{t?1(^>66q<<EDQKbzXW`|any`_1<eEigVp^Op$g z)nEMnS3my0zxo(x*<D^?*iQlG%O8FE>Wkk(>+b4%yQ}a2@2{Za$Ggj4>@NR$cLl&d z*j@h&PvXbBYXJLaJlbzC)jxz@U;gbcU;W_Ecb7leUH;SV@=te{KipmZH|qP*6=GnM zHF^4t55r|ye$Dv9J5TOBdgqY5d+VJjT&KpeP8YX%IlDlxR$N-otHWm(`SSK3d*%K2 zp5C<|edoLg-#uU7{e#)lJ1-udJ$eyeJbwD&#iQrnevrJIpWJ!z;NtE#-*4h~&+h+; z`{4W#jgMu-Knu-_#yejdjoymN<ibp{Q!~lUq%o7k+~j7Nn|Wp~9dqfLOV3>T<}xss zW3#GLb5YhSbCIQa0`h5QMYU<n%8c?dKKbsZY|`y2tdFx|ZXZ=)l4Qj(JiYATSI66^ ztdq1xubVK+Q~G-pmw6sm>vYZ@j%dTW3S)`~FxKgMZN?!MK{L*p)Qrm{HIqeQrYo~q znx|pCU6gER%nP75%#ZM!vot$iHtf|)nC2$m=GnS~Ez+Zx);g`iIt0wtA}_-R#y&Ii z^f)b&ZIZ34JiMTg(WYt2VjFMjby?4=GK2k4!)xFvAz=}&vixGc_udD_lXuHznZ<kN z-s5$667HFI>MYF7HE5gBI<2$CQJxj)Xh}$PEY~G8ud^5F+;OY(BeZg(Fwc&Qd7P4w z2m?E~tXKl72KSshwteg9C0aAgp_3?q(N>k&Gy-v}vM3RYQ-Ofl4El-^z>PytTT}r= z<+)oV27vCbtFp-v0{F$ldk(<wr>kQ_CfRWUXaWKXD_pN(EzJF<tg?6wSfj*%H5Gp2 z72iKiO5DcbnIbg%9YvU&cwOUvK{~@6o(wdOxnHHQV4&YIn738QMhK!|0j)+jKh3}% zq{$ZK5>|QNNxa$2C##X9aS4(FqDUp82R-whBnVK$vOG=et=w%>fQTc;Yo%%btPU%n zcw3dkF3sySht8+z(JCuOXIauL=d-{D2_erD-*VH{(E<JC;9qQ48sVlX0Vy&~N}Ml7 z8IW{7pR)Zsib|ksHTPkFgBK+{Ic@)WxBwcrl<V`oy(6|kutGSB^E9mIfMD6J&3elT za-J@lBg7ckX=n3gmLzGxjZ%cC*%og?0y#u3nXWUjX8tXXGtLBY=H))<I9M6rFF{wM z)gHj-awsN~j=57T$g40bc)B`R-|1ij&+-uTdJ_qwcF-QTY<^(*NQNyCpOpdn*GgB_ zGK4LGe*pK2G;Z)SxF{#EGWfa5ld}T-v#CrgI=*jO8Gt@Y&&b1*W!aSE@sF}3rH_xw zCW}G8#W}byMrc}_e02sBFVBqbaSdx1FAXA=rUiShSygC{!`nM}t3ECsFBS&ajQuJC zyU=Wk)aZ03kO!!tDKA0vvSLxvH`us=&N~Gfj7{Fct;-lxqEYd`9zr*+ElE&gs}Kyl zMWH|0*B8&Tglv6+qN+>SIL(D&t(QPvylL98x+cb0w5T~^4w>NxXITTYN0Z}}inUCj zM-(*$LIYk}ah$$~v@7>)gxe^LPmb#nHuor7g~#c9Q|Di&=KaLF04s<LJ8oJZ!fX!L zm3tVkibI&!voOy`_RMjdsZ|xnZy#~U>a<Ejq@?H*NK14Mb7Dq!Qk@$%ehIR0{@`4t zGcY9E)L2@%0jSnmEEd%I&K*FxF7qtGXaOZf094OMj)V3VynhsK6^IoU3t&IUx4IqA zTn01`^q)u3e}I&?G(rGv+!iFN20zjsVB7mWw7Q1$$pv*IyD}s6c5Yi!P^h$nwJPgW z*xfHdMwm~-n9=f3!|LvByI{64a&3E%;w7@VTgEew@(j5O^4b~Pic-~M8)WSe60eh! z5O!L=Y0{&b_#t>1VX4tdsO%)DoO@eN%`<AiS{T4@1N}>cOd(Fk_YlLG$;b+92kDw& zRy%~z0B0?-dfjaKMl9&Z_qRIkO?^qY>Fy!_jId-KKin#oBwa+YuVi8z+lcd2Z!+GG z2(w&iyp|f|E*T?dg0(%j?vkk=@C{(ZMRvaJ)*k<U6Z6Z=%2tqN$J3Au40L>df<=V0 zLvPZ1N_uyOOJ3>M#}#;vfQw3lxGlNfttWr6iprc@C^D3*l#5QQYRH&QTueo0ekHt9 zq1md76GtnZHkM8i+SNl(`_P^i+Hw2Pz8-qqhYqyRL@9~TV?DHDmJ>a+VwO`aw5OQm zOb@M?rDJQseL79fy<*CTZgyxEx4%|`>GlsM&JA)$RfBEYcw`uI0Nt1xUrZqjI|!w6 z5JC`wtMo<+1G!cfa+o=_54$O0O=J&Jqta?*>X3!oa?_w)&bA`OhMdjzQAMXZuA;Fa z>dl@=K*jxL$iykNSqP(zp>^0L{WKy7SQY|j2&fhB8B+Y8t)`RF)wd_JC=4Tq*IUFa z@T(?HS7oghX*5BL0$$}J%hCFZ91zqZ6*a>)D63Jp1EMgR!T%KA2H41xN1IjaWsIpk zM~0m>A_qc9jHp~F2AkxPw?tnzCVNVF7feWs2)=>G5s-AY068lz-vkkZmLa?o#%Aa; zuZ4IpN0J<OSTzwnhU*VO(+1ptJTA1)`hC(g<%OZQ5vl)>q>FHqH!?!4#7JmgS_lr< zqwoS*I6M=C6cd9*Z64;}8+^S)DZ1w3wqw72G+M1kv~jQ3x_3l>fESJ2nBiEJkGx2n z$VIQ}-O5WyyWwRS=U4Oq<__q>oCYc@ko!FCNYhz}0C_h|Fr7AvcLOYY?R1pgM;>z2 zN7y1U6hVsuKZ=pd$Z4@+&gh7mO!XLF4s%oIdd;v|>vFji<Cq%=tAhPyayP68MTmrq zR?w!SXb=hAsrjJDadr{n@pXgJOan<OhezaCW0dw1-uUPZbDEdV0IJ}K&!mazvVI(k zQaE(_Q><;4j)8L9w4Z~`0SSCilZEs{+el2s{%VQXuizMFMgTlObFf<q$88_%dG@;D zRtL-&?mzOzWYyuNTO{-)1V!wnSGO;1kG=GpC3)%iGxjcE?_AGj@5bz%N4$`ZFwnL% z`{j2cf}i<?L8OUF`L&D@ouQav;l<UP*)})pA;5s=gdPwa!70a`A;7WAv1|a)!Mlg? zaR`v+E|LF$@@E6PYl4LZM>c%qE@fIfRDAG4l?<UiU8VJLTErJjT!T)O75SVF-mt<P zR4Mc27Ra)ojQ{qgy(FD`2<HSsU%H>VW&wyzgAQv%wRBpuPZFA-{<O+2z~M@le{Opi z=-5l!Z5*gG4P2g%M@0|AhMfD!_%$#hd@Aio^T|_ddW%<7eqXUMwT^T=EHVzL4%(Mv zvhowPAmPz*0#=b`<A%lN;hAVFxX${#01z3pi6a_ecfNBR#|r%*BG#k{#E6CUsrWRO z8gp~r+&$j%@@jWyF2bpU=Z||Dj`M11C_tw&iP4`JRS*M0l60Ir5+Zm9nKPNss0Eu? z!L6eM#__SV9AnO$yEIGZDRYLghyFC~GUZ!S^oenOL;#<cw3S&&ZJxz;q--PhcJs7p z_~T&WNOUuE(JXqWXWePVmP--0t2Px^4as_6$s*M{@i15M;{<q(AN*Uw2zT2c?}n9i z$<ET|-o+Is$k9d-qampk3<>Ae^NH9o4P_3vmvZDU@=Zg4p^j4&u?^Sn>3Jh0sj0vj zL8}V5(<A|M+6LBVOR!sb>5<!xPDI&gK@c2?r@)-9A~B}8VS7WO=uy%@dOouwui+kV zlG&6q>m;)47E-koj0#wCRZ?C7$+9(aij)JrhmzXl@GFcXQ*Oa$Y*Hl6$3KIKEm68; z?J6#i$`kC-Ibb|}cieS>nP}ZB*Of((n`mn&$zJt?;j@Gxa!49o9!A<qy<RjI3QqOS z6Y<8H5y)JIh6#vwyIHC{iPkGas<x-TRqi0*0oP{Tgy0G4A{#Dq+GcA|N1BDxokh_g zHm@I?D-P+HW<iN9F)zzG5!5vny$E@;!w5Wn{6c*&$d7qbnh;01k|H9Ad-<t@o<-%N z?Qu67?O3iNc2e&upuph@u#nCP-DsRlX`2|FP>6V%xas6b>_%VWZmxtf@n?&8M%$Dq z7f~E8YzZZOiMx3c$^|rz6WXRmxk$p$mr;_JxSKDb^wSvJHrl2{ISZ0-5=bcNOWZAx zP(}&*qisr*$r$}fDCtYwZ7iXTeU$E_ZAz4BvhWvE2_=0=SJgsQ4rG0`S;Db1nT0l6 zG}gl%=#ehQemrGs!g_dvx$EiFjTp;W4}YMClTN30#5l@&1Oq)L9{OVpV?D+LJz7z2 zb&_E%)4b|y0G0kCuxMrUfs5cD1d?XJhu)~h1$p}Vgw>>r<!tOJjT~H<pwh|&*-eM- zq^<!MT^U@mkWGYSqqL{(H1Q%9%kX_W!_jsGJJDouuNN(`(eT^h0vJcD+jU5(Mby{> z;=5`Jek3)`A}=lzYICG{^pNEB5?go(n4LDqKyx1$E^5@dh!G0lIaqEQq&x{Axsnl@ zCGRDN(VV%0OtE%NlzZVw)ahrLw|u0DBSFremGjYJf>ekfRoUxsD~6cp|K$=4MZr`Y zU99iv&*cMJX@H>g=kYTRN_$rBc^s6ZMdnR-Fzu<n@3<V8_Bh`UcrYz({)_{2wP1nA zgXz!t9c~Jqb_&6S!{%wHFy?3KYnN)w&vc?4bj;7x*On7ZNOGIfrmayRwV-U}wS!Li zDFj+<Q+^7*mc3J93R=F+XnuYXo|ZMl%TkG@rdxmyTjI!#j^lHk&koy`o)uK*NA0Oi zcMQ|k^s6)*U{>HI=A@?1aF>J$W?W6|xQjbn(S%B8@Gi@$Xwy~kGL27)%_>4g13(li z+@l=Y^5;&dtw_Lh@u^cFgQYM%Yl6)rhYzV&_^}GlWz0<RuF5183Ymoj0ae#MghhGQ zN7ySkl&Vmed<qj}DKbp^frdqcwD*fZgzEoZbpxLb(pELed2^8(>2^V@?gLg+H~_wG z>asW%4kIK8G}|O?!YuD%ePyL0mA9cH4L}v<Ib-%vRk=`(f`dn~7!A?7@W8>>7sbP1 z7thlqYYb^ziNqv{`dsvM#hNxPwiQqk)nTF`&&nE|T5zui&t)Y)v!5e|ttK8Dc+|g9 zPY(GK{L$_Rfggw%xI@O*#fhdNVHs)pU>t09#R4kmJdDyD`p6Xq=*AbT>g;zzX(3K( zd;9J{N`sFa9l2JyShUwrW-~pIfp2kbQJqqh<)}ELE&hQXX%lb7BC4Kt5z5A-eTe+X zwp><e1cLU=aw_d<vT~g|VjH%#XhQfBu*K#@b<5N?cCt8?K5-=3AWRSoCL$M>Dvg`4 zPD9Q88<v)FxKpkP7ow1m#qQ(l9A5t5Oa`Ktg6Me#j?lQ+2f5Q-X+~>=RY4btZF=IQ ziWWr*D#fz#MgF|mk-91}#$sYEa^tXw)0}q@TJ8#2zaCz`v#DfS2O(%iq|9$ZsHoK& zkH^|>>$KTaLu6n8Q9sAtP3ROv#3=h=GpEYHoA<Slw3#Ib{k;hS^YuZQu=4XvlGYQz zI*4T#BDX55jY_t<dLgMkfjguWYP}nDW4i)^iuN>=yYV)fAfE@HrH&lSMu}`$gWA@V z2^l5|iM(JLeeqg_*DXjii^qvK<Qyo^XS$IjM<t!OsY>gMUGQb6_65?5z<oZxe7*Zd z(IM*jEIJ<9GuyW()^WCY+YoC+djo6J^N2wQT_OM*>HZ-&yOhTzO*~DKq<54n&saR8 z8MTRm9%CdynrY7CNcx0UpT+B18?ZYp*R`(s9EXjiY)rEtD`3_IN}q|w^M=^1>t`g{ zsXBcoKuLaaKw==R0TB9ba+CjAZMlphmii$twClGYuc|sb*%V=vnYso<sE|l}DYSN% zweZ1u&yyoZXkw2W@|mPSK+#Ekmq>uCw#wmQYG}>UzU{{LPx2(Esw1Bkg_WMiDZa)o zUa7NtL-jI`s@=lhc2yMo;Bp^%HNWCD#p)1I$L=Y*F1@EH6O9=9L9R_espg5gJji2h z9z@sYaekq%&LY=Dx=W+H7}7&<?m{UF{MjxTap`5Qx~zi+nU-qI#H*=;A(Et(y0LDq zkYNg_cM(=G;6Uhwg9l_7-QGMq5)NbrD0R{xp_(HJ#T{Px@`jduGgXDO<|IIS{LLq$ z)%5hXp7ug585DC;4k)Dq3qDt+yr-!osw^B#nN8_3#YPu5I_gFbH@fOZA2)icMs`t0 z*OZ6rhOtRtgM0*U_ZiHSyJa%g>SB>X@j7k)k}?^kJ&=`e)riL1*5{lQ1+fQ;UD}@u z=<uG~ijtb59Oz`@D^RK;fovcZ!Mc5+-tmTR1PA~I4$ovo!TT#J!P(LeR5&wha%)h_ zO2RfQhXQeZQR*0_2aJT=uj_KBdVG>qwx-Y87dVrWr>cz{CD^aiSq;*R{u4iYVu3S^ z0vt&jy|f4r7QKEU<`~y33P9+iDOCB7X^APhwaUY^H-2T%Ko5%Zgk`f#>-8c+7ank; zraRONEK(`+6;PPQ`Fyl0UyQ)59Dzf10yxjPig6F$64#;R?qzh>f+TR9Q)j1-NHseO zG7-?gE#|sAyr=!*u!)yKs-AuI!*Xv4C)&J(u_JDA2jEAVu2vOO&N^3*8g)seB^-+D z_;j*e#Xej0k<XFG=|-hluP_RF5s`O31+ExN&lG+O2f$r8#}#w&w-kh~vainF%$>V5 zChS-#d{9Va3jVl|w8L0249@-x_EY5Y1Tygj{H>6@Gq@vka=fPKwo2A&+*O>Qdu;r* zkk%<te7lG5fv;QgKNGRE*g|4_&?`z2G4Nqae}H7$tHTvs1O-VD5t-5(Y$&v6lG2F1 z{t4+X(J5Ra?M(~%lxN~47N(MfK-*@QVh%(Hkj8Ja8tW@(bnZ$`pg%)(=&TdzpgO4O zlturTRp^VqYSlk&tob__QU{+8du&>Ktw~>T`=BVB1#(W>JUia&{Cxw=VFqT<zQCAj z%k3>u`X*iaj-+J9th7Uoi6zm5tu)qyheMy?qJR&H1Zt{&e6AuBKM#Jw<#kGcdPVB& zOKP3Uu-V;fZ50-A8M%t_DqkbbT*RruJ3ybZvH+x6Rh}WhN(vP(p$f47RUVdf={^Jp zOHe!nqe2FypC99Kv}vdIUb!d@fuxBnpCCY%s&8B!-|btgKx3^47HX{c{%t`Qy@1!w zW|Q;#vfVN>YJ&CoG9HCgP2A}NuC5qcahB5LxY?NlbY4wENir3xX?pYtIV#C{(6Qw% zzV$Dv1ZYW50p>|vuF6g<9il7-=HB%(tZvm5s%a_>(#eL;RPIF1s*y{+=z<+Omw=7% zK8R=@D{sc}dI{IPJrjQ2?9mIuSTGEdY|bo*0G&sJNS}yXQzqBsUx7N7zNFNPM5pp- z0<U_sEPY{s-p8?cAFqpvcm#p9a3ikj&Sfr)fj@ln<{#7{l}8doLC3uB%G9z3$K^P~ z2a76LN3aLJ%#aS}c5vR@*cI<e=)gRlME(p@4csaecCS(a6{tjweZWKdzFL?MSbkzc z!&>?TM;LEas4CToeOt2p(u&=!%9WYj5>9ymEb7B~v;Sx9S(e*IlDzvXAb3ViSP(?; z{Sf4^J@$-u+IB~GPjAf790x!Y0gxa;0ia}!%sI681NJXAV)w9zJ?v%w#y_%IFQ5u2 zQ1;F`!W2-b%z9;IW>sZo;iajZuUlt|-LBQ`mOx!%pVj5~<gc9?!rp!Pg=gy3>bH~$ zWkl^ifYxR6f)4eVAfZPa1K6?enpG1k9bsdB+%8e!1$m<))agJ?Xy8R#wV5nVMIt?^ z(!(K0gw(RBvLYj`KfC1#0@Jeu9CW{=YQ;h(pO}%bcD0EIAw1HJW5q5vJzN(}x<#xs zhL?26M*NX9yDO;QRCyq))ouj=S==8Da3>t{RF**nqK?Fm>5*{N?M)6K^>Fk#hMAZ) zG5V=2COSJgpHgw?k}#t+CwLi5q)6GRzN09F<Tfj5yJ|wYoIRI~Kmr)Ck096ZgI{#q zxL8yiIaOq!uzsnZrInv|RT)4DOjaA|;U9yjs2#u@f(;QhUMUJE=R>Y#bXe><nyITL zvB_V0_}G-c*lp<zXZ(tlo?M)L{G63FfS1vyLs3f~kaTee#qNl9^d)6fSWIoDZ_y=N zfwxo@n6>Ii?3<xRNQL(R4TB;Zgz=0Z)P;P-ic=axWsJ!J16xdsX!!b?YjPyOi5Q-x zy5zUHg)BqSscj&!T^MOltJ)V;y4~#-<~FuXBbz&{^GA$|sP$pC_q4}e>TZ4Dq9{Ic zB-mA%x3FD*(a1%qvO_7>-QTW1d}Kjg3Ku^i*!6wg4pp!jP%<)N4N+2~6gj(g_^Al1 zTMf3Yh`<eCzMH!W6Jz)i`4Ni%%uy>kP^cdR5L9ozk0^Y!NJQdfui9EFJ-W*54Y60a zx=6G^#&2Yx;}$bKcm#$dXa8-h+u*<@G+*?<A+z_$p>Z{ufMS4TxhDFQQ40_yP#G*) z(Kyw9Nmtk}A-q-X+I3#U8hK#1r0KYsS<xJ=a+BjViR97@Z{;5=p+tK1%skF?lc_6A zrgBLjHXL|{8dF8&OBEQEoBr*Fim1#-zQ#3J5$5a2HrKdLDN+Tj3_&;gYNs_DD+Ujv z$ipaYz(B*P6&UDg*fA^oCRd@#_8CJZuH(y8rOm1kK3;Z|tDgU>BCgaXE-ouid{@Qu zN`1ID{4G05cp|G~y-H&Us&5L}zw>TESq)K27;sEDH;?qLwoNO8t#vtHmmT@k+>I4; zq({eMJ}XGwJIud%ak2u_74tT&OVoCWePLf0PcORC7iVf;*z+$u5mZx1ZDQCbE}G<z zszq2D-s0;jh+&wx@81&#xI830Iz)2UOZyCE|Hx+r<O{KP7u#eP)g??{z*Ge#Vc^u8 z<|Nb!7Xoc@vXWMV1_cw}&G!}mrpob+?7UjYznY)*nEb0ncaRIxtPsA)$34{?!g)}; zO(*4T3S7!_skcN$dlbVZcG*#K8E&;k`D9XR#qbw{QK(cUH3+?4J6k)j(Z>g?!fg!7 z(C#EJ^Mu2BVZ^c=#W5|VE^H=DnbrEtLmXg;Rj#~^B+}iHFpEMy;j)OPohCBfN*=)7 zGvpAGw%`@P6<U=!ozj!bT?`uPq+D(HBp(du8LWfBZx8=ABdWz9A3m^U3X6+0LRq7) zCv~NLoV)vU-<Zp8Q$N(l0fHq_)SQj_p*|-54~=%ucANU4K8~9sklHr&7=C;crNNP0 ztom^TE2C!1O;-DCk)*q*8C!={KXZd9&!OLr^hjsSo4QMZSEbFmV3)PTz1|;3*=3E{ zMcJZx#BAok4HU9)W3=_%6jqgrwG1Qnb6bvOS}4usbXycQ@I{s<(hjw3ZO}e4Da2~A zu!_fQSJj4#btGEuOXmfh!qf1HnB1ikRx3nlm9^Ed)<leO<IkYF?k%A#BzeR&WU2{- zTJct1MqA@8r&rX710qB^BFn3h*lr%+Ob#s7aQ8~{{*ZWWP9y1!2q`yuVo8QWvh55t zR&C3BDt4C<GQX8aaG|A}C(`{riXE{*5g)M7kQ?hhtd^G7nCYOB!<Qt}lZr0>e=>XD zc^5ZP?qY#YJ!Zb{f*!>{`Mz9xxx;uk=0>GPMrFn52F2=o47hi(0ze&9p-=3tMrsgs z{0agxYyeS*M<5`!-2kJGZa`uBO)%<+t`ulo18PuD2gPytgV9>#!D^Uz16q7#vb@QH z<N5nMe{pS=7>W11*XZCQ(OP-uR_UN5-hOh#K}ZI!?xa;RNO4#y9OKx$sB#l!Fn75q zOVl|<X43m@gN1WBUj!^THl4z5q(`o5tCx3#tFizjb@WGI#$yExc|2AS17i1^9oYeW zz`Kki5FM9Cf{-dAD;$Ko)D&TuQsMC6AYHrG*oNU)To_d-rKDVri=NskbOD}0y{Iqa zX;j2;30NNYup>iqPQo_+$Ywpu`J5ZcR&I2l@5)%%z$or+VNHGXK%mS;4QYhhD6wuS zlaU+^*&vZl!2{Z?AMh;N<J9x>xT~&HH5ZACpu|x<NUMIV-TYF|pb59Y*3{5yZhX2_ zP3%aq_S>}<Ft}=DhjzW8B5PG3f*hTqoNi+t>>E=BgMN@VrAGCGwgEL<!ZgRhD^8L% z&p`2pJXRTUxvAiPQjQz>a@1OZ<v2#JJc}<`mT?qP=oGCQ+OscXsKwc>?20O#U54GZ zEk=c?Uin&Juf^n*PW?z%7tJNyMbVrZ!x7bYz6s{0`IhV}Huf3MvXeXuSSM8{I_GT5 zB*8ngvWZWff@wD5yUQM3dMmO>%go}wxXZI~d&L&xIE||&B4uRk&ZsyP==2ETiliy+ zaI0hJ494TO*Hdl2h$i;JMWiI+{*kjje9N*XUR+wgexUrC=T)xH?1PIOm>Eb`pW?#0 z>cOY-+*1V!DkdHsk9{naS68j-8S3lYf|javqM@?sIi&JJSHTuz#OzhSeuqrJNCMl` zJdso-zQ{v`nV}FJp=#-?MpJiz7?x<Nbb}v%!tsZXBIwAY4;fVcr}7c9W;b>%yDPUj zK#&TkA*#%=5*qL;B4r${rx@!7N3*Zq4^UhlAUNVvov&M=I`z!~!{ylb#>eZxf`wFc znlhY?CRU{LE#A=`9?S1Y=|Td%#-Lq^lmcWm91Il_iKr>xgt~&cKOEYlr2=#PgWRfi zE#{f37DT<Qx^VRxDVJnt1R-np4}jDITNT7Eo@LQPrseIuAa4%hhYGuB-0v<`2|!lf z_r%bOUpwWbVgY6a--F^Hw-P{*-G^J?__}KGbG-8Q=OfXQ@LXY@OXYD$ee!rc&sSc% zf%?crUNe0(+!<3^9LkqeE0^4&zB6XKvYpz#<$!JFb}EM7QhjJd;0K(HB?I&Os_v-1 z+@J)(TXoSw@I?TaCZb#xt|}~C7`5_ZcB0>GT>I6wwZy)X`<6(ywM{3}<s!CbJ<zY4 zoqpBsC6tK&7co29bn8+R1LGF8YPPuTjpX|OgQm6I22*K*$pZ0uAAbsWwH1saq7Tbj zwe)0onS&0y9UbSB)OVZvmkb61s^ZRMVLX{3DlE=ZH~xV&U5en+5y-v>btC7OcFak& zw82mqXPPvu*ya5rx5#Up3uxul*ota3I$Mr+id#w)2z+Uhor|qv9+&hC`O*3L)#U}b z!QkrAWwuD8U9RPKJLKqiX>_r2Z(N$HUtOM@pFg^BQw@l~(F3>pVfj&?K8ltt43I3f zKR^4!yLXPGV=ulXP?@iP`gB4Pje877zuzAY<iXmy8$_;>ahCfn;Uq1Z&vl^g`wg@y zGd*X!k7Ls}k-31Dr!C)UPyI{RiqSTJ6{GL?XSQ*_a9X;4=Fa@Nfj#7+yro;ZG4&i$ z6ahp|YcXrjp?)MyH=f!+6X<fmQ6UFmRNibcL-V=A{(ky&e*iq}?Qlp@zE8KBcbL*v z#7R)r-!olvK^x)IqAg%K0m`lKlORCp6gta4J;lzgfpO1Jkv9f<KyilnhouWI2VPK$ zf+&M^W?7nVMy5+kL##Bky#a3E?_-pi3v+1(W@vi2)DwjJ<{tk#SbNO?K{lDqO>ZB? zK7KkJ77qSAT{^DCa!dnwCOM{|P(q9=0t@SGx*((I`0&6$oFzOH5GN>8Gi$fc!q(E@ zA6Y_x6E<$NeG$7^=0s$qJQ!l{GUVd|osY*QoO$yW^bj;AGVUYmmzO{O^!nXfC@->} z|LOJjzjFfGd&Ri4^a8UMWm-X&culL(Uefe4-_&6Vu+@#Cr=f{uV`2PRhvMEUT8=xc z%x=LD|H9kPhsP~OxHdL37w8HTwuoczRwshz+(&5AHnAn13x5N&SSRrJ7J2Ik^cQoJ zcu|ZdWQw>^3MmVB1vwAq!od;#0yj@mYoDR?Spd_?bTKKkF8=t3pWl3cVOTlJa_CZ^ zd5fZS?1Ssz0m&C(>*CpN7o&eh-XEj<{Y7i`*Z0}{iE&|I9Pa6<JG18k<jzeq^a?Zl z7Wsm6U1)De1>av>h-J}QJ37Iy(?Ju8eVeQ!_bn86F@M5J6QEp}!w0FBe&V5LuYasn z5d%S|FbF&ccx4T-xOcd7XYVs8uCa||0P0PwV$t42+i3RJX>+}+ZGH38>zD7d7J;c3 z8=+d6H4MMUG@)!ep%%v$Ynk3mNXGYusyGTzD-r(_aJ&EtJ3<ba0P8pkuGuoCErH4_ zlAxvU^C;F~MiQ<G59p)2!Bj$u5hTlr<A_q{!y?%(qd;Q4EUn;HrmJFMAYt-!kEILd z&Bi@uKnq|%i&DlcoCRh;xSg{F2oT8SIfOY+y%f1?LR=mY73FC@=`sUa$~X}y`%d{t z1WKzi)0aX9sF6NPycM=~ewM--ZmyfCOnSv2A=P!>ku+<UCV7JU1Lplq&y%0>6eK-# z-;EpZ(YvKSH+|qG89_HG$}VM)YjGgUg;{wApB%^W4rY`+Pm&n9TNM-=fdI1V9?hm> z5MITvF9~6=x4_0!AQ(SkmLw0hmwTu()T&NDJ%zf!l*|$2!_c(56ws~U^t2^npq&j0 z%+e~`1NjXDl89UebhI50RVJf@fdT%+>GvU+lF$$4hu1G(LW5pKA15tQS^#@6o)&l_ zb45vMjfFmuqiZ1Jb*bO1oi!%oz*)@JbCBn&Sun>$FSRf<PEW%V;8p?5<_LbB5Qupq zkn6z&#Sm?UCWKI8MCQi{Q1?k_+~;AM+-h5NtG!H9sEJNNK%)Ux=$jhMXg5O|iLS5Z zp_^-<Cu=_F#`ws85ur?TQ=~=PMZEWs531Y&H*Mue^;l0=CFMIo`8*AUbX<!SOhzHi zBBqw<egV?a3E@k&2ll-nDu{>%S`;)ufnFrMLa#RD3z)t_#seCp(T-YLp_{$C-TuI= z>|yxUARC1`!WcOb{2UHTtE7OIkOTVsa4_9{fHmbRWruu3qJ|N92J4E{D@FwLv}k!J z4FT`<SWw4TFw|=&o^=T2tWQtZm=Od2q%B-Io$2Z_<`lPbR#bC~XRA4G4*AQZAkH6@ zJ+g*+()SQ|1g1Ppe%0nnjs5|5g)o)OkhVDlT!E_16zppny>e#4keWib&rPbOM$p#i zvgjJ#=E#X~n^#m86rX1iNWrBgjTU2oc^E;*0)O)Nn_bzOgiq*=xi&)wl+VD??=8zZ z=;qeZmoNqBOXTRLrkk;&YtEIX6Kx;QX<A4tF$xZooas#6f;<WIb(UjGQ(!v3b940R zQ%m0_KKdCNspEl2PEYmR4c>xurtbo#0*%BygdAI78!Lx=raZf~GK{CINtd%6B8vz_ zNc&2kft1HRyK&k;WC`!l&E<N!aW+f@i!GaN<|TM*b=3xbXyINYpUl;{ZA=2DGO=6) z=D=PevYOxvjVqhrN_MRxgFe?~aQ&Xwk#>l3E+^)utk&i%Hm3jo^?s=02fa|bLTi;o zTUZ|hiDi2)%cFdsBXQd7%`GLhYcn-7Ah#7P8)Hmzxw&(6Tr=I=0DbA^4bUJVIUu}* z^u%;(++PFR#4(;;<K5JE%hnBP^7UmKG$o=T0VA{`_AxTA>E|>1%1L>S9CDD_VPT2C znW+IHD+fV^W&bpD)&+(QfzPhaal;|Kgv)?Ru%Hu?+Gb_|CGN4%11M<e%GtI^8_Dns z0|OL#FHt8>0HH(2W@Qq!_VqRgHe|OAK0pqz8(p6!PFPfMM=F@G3Wh>$dZf%%p#(Wt zlJJ*LpD4&fMFL+fU|Lw)<dc!idzQWeNq!<vKLFJKey*Q!27RWV|6QNPGyXnz^1~sJ z2BHmvb+$SalTzs810MNnhXbD5)9WkHHm;rPbE0AdR*H5p*61<&{SgH(Kkizj>}yh} zVFDSQ#k1@A8KxvXD9JTAm{86}5M{v0)4ThEnmOhRSb>~<g8#p{2uw`fBvPB8P=V%R zZcrMaNk!WZ9MB#jQLH%;YY9`GLTF5QxdP$~G2f{$ZkRIBEQHyC1)osw7`SLE%HS=C z%NQg(C6e}Z>uiboPuRKxSezK>FyZ`xjT~s1_Zw%6r5_&CjVQ@5b7s<PgYq!(%WVyv zkk&~_y%8p7XBN*7lH#;sa%YQWDUfYzZ9L5o%w4EFtQT5%VoV_6=V*hHeNNt|w2}7_ z%QRV=^pXa#Yw=DX$TEv(GN@@sfjD90OHw)Zgshf8H~s}LRaC0EFhK)@D&$2+(N{*{ z{FSX(a6cXn(<*y*T5csp(4s+=Ey)Ko;VNqft~^m@Cf#1B_bsfyU`<-o1qIzMn1kAc z^Bksrg|wEYPw9%S9`S<M`W`PKm(zj_o#Kh8KrHLgk5mJPZ}dS}pYn~xmVW1kNurx1 z+2p%~6TM>%t+q~g3MaZf9FO$_NI}*O8cI{BHNwglom`ksU*qlYDX@)n;pl9$+_P?P zZ!J;?^b6iJ-{HFso|66KGx$BzPZ6eVcthF3-J#afI0Np$-;nfHP^E_Z>p%|)tz=74 z!0(i;GfT}J9_+3-7dQe;wgq3Ax#xd-cK(++uocB-9z(iJlg*3JO<#b<)M~kxj{X<@ z>9~y#nv5%;F}y%vpP%KewsB^6ji>I!J?{*RgTeRxgq@F9JKa#E`bFxio#i2SQQ2ao z`-DZ8!>D>+)#*8OGKRvMU(3K7n0Q9EpodTOGj}S?f1$fPwhp)3@ULjZE8oJRVfP~3 zZ`X>-xsq<6pPZl5?Hmv52Yg%LQ0xzg!l&hdJTWN4^c_E05Qa@&f1}uGWlMNSDA(P_ zQy;hPWsETB^SjqRe4fxX{Pa=LwI3qBhfZHc(%Vpa>-RxC2%yWQW6SS1;ue_Rwfa_H z#{yi;wasKK+f0UP*C4W`43RA#f=G5U#Kv97RSlCYCtLX3z0u++VlI4F7OVt{0T4Pg zdw%ybT_d!<!`Z$wX6?BZrf4Z4Sfs$C9Z-{m#9h2dk~Q?ZRK-=4Rr0NNuD^-4wmP|^ zPVTCcd+OwVC3zojUeFp3y~Uv0ANoBT^~b0^>J99Ezds!H`UoC<WZIoUw=)>pqqc;B zH5lOh(ZU~(mhHCJY4^~0)NYT*BeYnKdYvJXV8R{UR@B5qkd=AJwi`tnqwb9#Rv9pa z@9v40;$$#3sVCxO+%>7Q;iTWCqB@-nhh~hD$)Il%ufWM@Y!b)6$pCa@>f1LN^-RHi zZ!+wdg@@i`JTSQv-efQ`sh{0s)INMSeROd_PZG&l;ngF2Ryd26TAXXIUuuK-)dOdX zmDy!a3sjdUGZ0tNat`<lw69-&6UMK=rwzXe<FFB>h;#H!I7iLZJc>jk>|RK@SnkM- z{owf@0{27D!UBAi>Eig{_}v<wrG=nqKFW^M3mgCPgX~3?=~{|n%wq3il!XXEEriP9 z3{Y5zTpx<U7NYP62UL%IN54%${Y0rQB;ky5$_4}mPSrIHQbYUd(xtUcFE(*-b%{;O zMJd-%`HMIS5-jd?&5c3g{TYkF6YZa#{qXFqu2CZhNAHhyjaY^_dZ%OKn!C96Hk7=N zX>~&EPp^}H;|X?#&%|8~mecq!i=lr0TxZ=1HT66gVV+L0#*u`iv-L$JUw-}fFQ5PC z%dh|W<=6lI^7-GseEu&1o4a4=SM?l9yxmm7`{qug^$^f)?)0l7U5TWZhf?J}{|heq z^M8E#{ICB@-IF#qacukf@hcR)7mRNiOEaU*0f{n8^2lZJ181R9DN37d#o8jt1}8UF z8p$SJF$owjh8SZT8wkc=2w)rdFLyjzKJgdcIo-2sM#%Q%)_W9XX<FUqbf4}%d-uQ> z4X7N9)9|YbPz%v*fLM4+2J=oRENFX%;50v(cFtn}#ZL(Qyujbm!0C-e6#6T_Yk)cW z;_4LRi)`>15S?UYy&;@%R~kD6BfBINo(si?Td(q!`%AiR=^A9o0A4`Xh2pTluau`Z zs^ibB>knW@%Y$>J>(j@NmY#2vuPxVfQ{EYB?wDPLfp1Q1?a3wLhr+3J&gEzJ+I356 zN+`?=h1cbgVQB@a1r!lLM@;lXMiotT+a-o(JB#Y*@uPm$>GiU09@gWwfyUrJcJd3( zmP`dB`?Kj-#3u99I@Q_jRVTG+MMLjsnrA3=5$K?lVF}J4X06+TL_1V?1Z|*Gg=vx9 zMl>LMwu`abs8#K^WB3sP#iIBUOTsdv-N2Hi1yx$OU4DKKW{4K<(RU$YrwfaQ6zsVm zbPCt5`s}okw_n+is~p6XXYGQ>UWfvRRR;-cItG>{C|}qZj2)cC`$7S>|8+Wl6auan zJsQRgdL$-6&sl<}D^^*<^^ve<NMZ>INz59_wGFX2SsylM2D@J#K*AEXI5q{~>%(@L z!4AbG*dc4$7q1J&$3kJXK6tk|?ZXoMh$Z~W+doP(uj&K$m;n#>NU(dYX+FDH;$Kwm zjnxP5H3JVqnSu{nll}IpII~tCw$BW9IwQeOTf?5ZBwpofU=#MZ=U^JJkT?Cxn=p4V zcNqBBL6TD~?eMf+`?FSOvD-+f9LxX&i-6>ov<?`hiC9i;9?B(^CX^Ro!?b}Bv}d!i zY|d_4dKi=&qCde>(B9vNwbO>WozOwp@{fsxeM0_@2>iG>v?z|w2t07}ae==t@Ph*X zR^VrZ!c`$(kh-IAp-}e`&TasFI(fRQ3mIt62%f^O%-<GA9{}qGjWiOGAraPP51c*) zKSb~cD@nwnBnq7V{BudlMDS1CgqJHFI)d*g)(9`lJ_&{2tJlW_e$v4HgW-_8z8Yz* zEG6uaK@LhE4P-^0`|4P0FcLW-C6(;6$%b-^&1Ub74n$HHlJ*vxqV4}ZI?zs?XK1Y( z8#hVov#CYfSn+m5iKjK%3O}KD$>#UlIG^nw|FG$bTuxiFitsGiRFuR3;J`19Anr>8 zTAMZEPYB|@du_U+9V6B(0PH|G9t-yx#;t(u>P&Ta$D^ju|7)Kzj^<Vn-R-$tCKl?; zMfch9=mEaAFdofCF=(2C#ot_OylPp7q%~aB%42+z_C6ys5t<Qg;5S_v+6+IX24I+g zQO2Cl2J5!Wfl5eaG=AA$+h~k7#ZRf_SDl~l_tk9_{E;@oPni|gtDlnUr>yG(S9WOh zCkHBJ^0qQ^fS$<Vpw0s9>xmd10Scb#&)Ic?gP5MO4R95dZJ~IJSPrjXkAsSfCIoK( zVYQrpR9Sv3uG|v%X`wJCbBphU;tZ*ISE)&>eJ}9{!d@4ILcxfK9}@~AL{VCku}uiY zBH}FkE-t~ZJmAqP+KsRjd8zj#%-n9okJ+T5N|VVtZU7BNj3_GVi$fIqnvQfdHRHzK zr}b%KfzHQi3;1npbMw!8{zKE*)>sm@G$xqZ{I({|Oi?2r4D(bx6vT_$5-G>u_Di~X zCZf~JVWFqNjF;O)`*VJ;&*Snq4oUSTD7r>=3qutqVQXU)C!2@IJZjD`QAH{nM&57o zcH;^o7Xs9nv>$|nGu(7w&o4HNa@p#T1$~Xb)XbJ<sRtFkL9$N6@r=g%08b3r1(G}l zBf>~#a#qQ~s1!Cv0knb|suDp>6x&BS>s^DAB`D1iv5g~Q38HIcvpg|z696Wz4koT~ zOx#|{+WnItdu2K*R~=lg#&Nj_u2rE(+%slg8NlVPgUj7GE;qsT(!|%I!R4ug%hNb6 z55WZ*)B}RCsGBpQdxNqnEyJE@JgsBSi5^s2i#JJ6Z_svW3oT3f=*h^?ve9%f69hS} zb$-z6QnN(1$c4ZxP?^Uf)#Rp!r>NDfRsw0QS7GJOW*u%9>uQ6c{v{MQgyL0MW;_xK z>y_p8()Gszza;R$`yUGYEV6&%nu1}8!waRGmldMu5wZppUS~iD1jtJEfxtg`zd0-9 z^8!CwdG-=SO?l)2Rztxe%P=|(vdLeAi!<PII|JXuVnSiC<7@{Q3qqc6KYI3rz%L7h z4M{xlOVsNGB9Q<kFYlBngdWdR(F+P!{Vo~3@Vz}iK57jxY5vq%!Q%%eQ-}T<#UQz; ztw2^`Hk#>=g`-Lp>g`IWvT{v%sYMJ0;SgG6P^NeUw_bsM|Ej#SA&yO{@bFR+$%w@k zKsmdq<-!n#sD*(9Qf&ex6oo>0bub=RkjUIyvlHU@taTd_`T@Cm*qUn8!GyjST!U9( z{w0SrE1M~}>+~+fFryjbK=ueDafl@{(UjS)YS)dfF{^LP)^#%*8@u(5fyb3@3{~cE zy@|iBi<>VjTQUZ`3?eR$3B^T%`?YmL&d?B+zPRw9w0gt3iObN$g@b%86z>o!4_Y@g zj1Qgz#D6+Rw*M$0t#wyfLvxlizeHrxRqIwBLo49sUkn6Ke`48!GmwPi^v)QwCI*tg zbD;9_N%`e1%O)<vGIKGK)C2xLXW7bY=o>r-N>2-0>+9Btd<H~5+&&Y;t2b{wBClaY zUKr7@5200MW7K*98@lJl?p=OXURW>Pd1u|qfD4|DjzP0=873B<1Et#&uvdu8AqyX) z4D(vCUIWB1aqt{~t+urZ7L8^h6X8r5hRkETg8ivXEElcEX1C90M?cJGL%BqKK0DeV zpT*W6WVGVKNNMJ|meC@g1<?u|0z{oSv?vrtKfqAC7_Z`=+ty8$SrnXEM<Uf9i!vSz zJ~khKNxpek@kg6XX&2+$9kxFBqGGGgu9X(wSYu<IyTaBTOlXEC6rNGLxeh*NSNNn{ z2Nk6_aaXt0Yw&q?H@<;jN=0U1ye8W!9SisNrR7Z(Fm52S_)rKJi9o$aWbzI3^=H*< zTLaw&+ddam`mK^9?M9)4c&|`Ra(B9(&Vi#*@Uj^b%}&QhV8G>tx4@^$w+2DE6(_I5 zJ}ke0@9zjTlx<HmO3kEZ9WD>o<_yT%>PqSMh`6$*##m`U6SZ}fV;dlVM(l9`r@3C9 z8b=88E9jkB85Rm-sEikQSqaugT@79;^c@D11J+7xIFZi8di#>WP|Rp*)aZJdq+SdX z0?=TF8ZR-}DcCNF#!hA9UWI>Y8B&f1Lpf>AiIJ#@;+fU%>BYW0e_Q3}Ye<mR&VtY9 z_5gcb5+C0$Uz-)*4OQ*~^;X2yr_h_ybL8Kp;*favsch`k8U{e3P{@QFCdm>qMQSIK z35Y8rzyjWH@?bRiPJ(^Iw0NCf@zNzzu0UI{L~}#c3pU`OI5bkaorl4A1b(b^d)TRK zEQOVf3m}oE=Xp&AFwm>&?#AQ=Wo~Ob#eo8lFI``?eSQ4cnRbi32lLekl#KnPtTRlq zG^6WJsF++uM`dvWRbGCHrWjE7Cp9wb(VO#mJl+7;;&dW~ByXqe;S|@4;&0}<6%Nno zhAp=-D!zUW7Ov_RMN@iIT)&|S!<Yhax!kNTK+!H$=bu&QXK5?Y&nla;&&>=o!}y%K zoDC_W4Pa(2i7WRsY}kCpO?E!st`W7HOeB)IJCu~7i!d#x;{yqeJMxNT<fD{ChfV3m zV(IpuRsL0JWK5iy2gV~5mL*psPz030!i2zILf2$qH?6HVd^8?bI(rPiOMNX3PQs3+ zlWId0<I}{AULeUc%i0+PhS7Ls>z>4-KBXtekNGsAKj6%x)%+~14k2Hlk6UlwiNg<Y z#Q6d_R*;P5Y{xF9<EczE<0JPv=u*4_{|YG9eb0i;FZcayT<QARSiB!!W8U49!}a#) zCTM<@wTCn%0T=J$8-c$;=tSpun+*(n!L~gg8hpkuuV3dw!;3Psm6TV@e@LY3l3Z-5 zq*HQJYcpMRIW8)6RYL)_#x%`hSfe$)HU|C;Xcz+yeA87bF`z*LC1wc`FUCu+Y7$%w zz*!@3Ts1kM8HQrCD9BT7uFkR?8k+k6BF=y)n+DZaODQNY#bESgSYmM5sGQKDN#PxE zS~O5#hK9cb!hUO`Ch5pRKq;$;Bt#j@yF#kD*q6aqD(Y2dT8kX1gCC-a0K=B13mhX3 zUjpk6B<4W}6lzvrG?DHJf<<W88w6br3W?-|Y9b<$U@SfW8g5qS0PhMrq^0BdAqWFJ zAB<(r<DFTKLp0Od$+e+_;KpKQ_+jbpLqhstDoa2ov5E7~D$ibSy_zTuUnxHtGgY-a zdQ!QRs%)q65!G$_#gI8;F%Y(afkaY?Xf_uMJ5VS6DVXdH#^h1Boz6C}qgE$N&+e60 zCsBu+nyk)^SC;eQ<S6El@V9K$``61-BGShg0=JzZNEa1K{dapgA8g94H<!Vr7jG|> zr`N0eHCtuz9hHv2MnSQw&O9vfp!|-1v>~{*GlXnUEPWvp4fo1};9M+BLTQt)48O(| zbMvwdEF3ThN>jgyzr7&=XV8xB!stNIezZZjw=;-HrZ3rhK9T_`lI)gO6KLLEdjJ)* zq2H^wz&5{e$X0oLUtGC~3u|No6+S7ZvWq~2<p4Yl41jV{N2<FIrXi)@%(x8}#<pG$ zmET?#A724uqCELleDhk|oT}#YAb=}#%W{n8V@!az@&5OuGTG?)OfamWJ_qy%i}1>u z>is{(%d_IlUCcSY^($~7AwOQewg&T2SzaSir$He<@zKWNYlOLKMW-V}-prL%xRRU2 z7(jkpD6C0YZWxV|qSDZHarlln^9R@xjIY)A>23!?ycw$ez-ttBdAdg9TQ5m8q6FPF z^v;=9^MN1A`6GKHe%8r2oeab74f`?jgr*VjE10dygQviJ-){~x+}6fl?>9%Rd|SC( zTpvg>aY^aqCpi>tCRphUn@vS7=6#zFy6r<VOsL$Q38s4-)GMY;E$enm8BJ+|g1!Y^ zQ2{h14S4Pd-muA`5A3K+<^4|bk74%z5c?s$KbFkx?~g?<VDbKJA{dWD)9z;ZQc!YV zr2MvO{I1C>nx!c}m7%HfqrX52M^(sJrsnq?Ob2WhYsR@d`dOk)Qw6DXdl;0Y8;gt+ zG$of0G^Og$#MZkxswsuRd=0dJ-tm6(4*a~e4ibeFX&rhs?ixuM>cXr_<M{yXRyM0Z zd6wBPRN>VAvhAEpJOfHe+ZhaH$q85`-v;#D>^_YV(|ka!*-C>QH2+dfaEhmbkq&$_ zc+1a-^)u9!v-TExVc%yOSS?udGkZ3d$_(H+Xbt3SG}o8LZ~HW7cIiF2P^!Bo63^0x z=y~LQ$~~2u9IR|#XrKMumz^i=Eg^bX_7lI|-u$!qx!fR*SQ?SDCD>AP;#ENbIUvU0 z)!dAyckx_2z^qC9q8OW%dn^pc8Jj=x`|*B<+OcVg-J(*Y`IiHGv`J0|3jp6rrZF&W zp^L}x&)K*w)QwSS8n&T{b?CTzBwJE{$xcRdkCm&9b)&EE32~e7*o1#ny?&2+yl7j2 z-ym*3biJ7eolQ*|`O;&3x54<Qoz=QTto@fxr%TQz#C+`~wCwY|I0=t~k{<;<WP{}Y z63I9ld%rnr<*KT4f~NDx;ctxxZlt}1SIzYS+w`kDD}d>M(m8s3iG76LyVk5L!i^@~ zRnUB3kM^hUn%aEax%O`_c4ohD?r-|u-?aBDKg0UE+PGl1_fxj()TdlTTLp@aK+%2{ zz8r-w*jxB|n3Odg*rTqSWJ<e&Bmq%-N3NZo45Wwo82Nx7Ll6JibteWxoR05?4t4my z@t*Gb8oCzqwzhxKbLvR&xc8@?o{kgguf7f*IhH%&{pLdZ;h5*l-cCQ`;<`Pt?raje z`z>@=Jp>E|$Dlx+2PilL1wW+EOIeLj@OLP{AHK$#tqgqo2EP3_e4Zm!;mhang>!n{ zK9AGo@<GA>AXTvHS;PmQU&7~MkFVX$d5`Rd&~}CO>!~bp@Eq|=S;RY(-^0o&2g8{L z8@-C#uCIO`9ZrP(tB~SZ6e!8C>{};^Qdco^2~RhyROT>tTe`J^?q_})<NCvw4u&!T zJpaJs1}YWT#Ns4+%J3}2JMtGE?~)3G(sop`)#7-(lnT^&_zOCfxPBkq8|&-kYqO*o z@pR{TVhzs|Q*3hU&7@GsQ>Qt8M>J4*koHc<7ce-2?mDn}9+JW(xyt}#6`*ZxOo8Zy zEnnrq&R)a4FMCWSKOZXT1#R#|OC(u2+fkrUKGLhq1JRWns4Ru4GGwNYshH&ws@gzn zj%5>0w8JL}CmY0^HvRz_&79<^)JBlxCSdiFEVoO@_VTkrVH6_~NNJ_*tRY>{EV-{< zpiIKVfLU66BfcwQ);C!)XpzodMz88P;G3h<XW`?OfWQ_JhnB^eH#mJ@onsckN_A;S zPTzZM$geU5!s*F`e17x~j>v!&blyo~&MHbLL2*e?q-Ceg5)+Dlkban*3$B4fBEoAJ z3xoXyyQeTl7V8rdGJ*1G`1{GB`Y=59z^T)xY-c;qoj67&ezaO#7N3rlF5e?(BkQ&G z>c@e|fwpSNB(+wdygnSCo$`s5f97u+0RA(72hT_?j0lBECI3E7QjJxE$%K`i4<#A& zO&S@QbULeEJ9dZdxE;20?XaD@fp)d5=Wy?-zJw|W@ChEeVnibJaFzh&Df>?|Az+un zpUB~tZxYUk?w0a`92MtuhuBCgA(9JJ`OD(v4W*Isgc?0@Yl#?U*u=ffsHfDn0f{w% z#O??ZX99`a5hRxhB-f50xlJIs4X636Vy4l45)Uy|l*=Qpt239(Rb;T4NmDQ;97M5V zSuoi(4)0_t5==yO`WvxWNLm>(gayK5ABijD<rnA!Bi!jOIfwH|b4uqb^1u892B_a+ z9r5WD2}eKCDch=yksL&-HzpJ9H}}TKr=04k<{c;hjheYHYoeKZv)-;GD=UoJDm4mY z$I1Ucig|`<OQb%kV$S;7@Z}wh%hix#zB4-4)5}rcHs4s^b)5O$Vp~ZB4jK+yojv}A zpK-VKXETvZ&~ehwfqClT+&)(;=VVw8Ob`~nu|PGBC1pN_@%lK&QNKz$$63wt{;cyz zr{f&_>Nxgoho8)V*TwmqjuQ>fq~oyPtFd1kU;5RFbU8g9pU=?_9c372o7d%|Ltx@R za$F+R<~ZYbI{qdN_F^a#OzOuQ4(6iWsmy>QmkIVq;|>>qb$~+paFwJ|6iQQIZx3s= zkkaA{lA*d%di9{RHX^RPBP!D<QRb#W&;9OO+u<|qr;dJa&ILZ=zV@0}+}saNXEM>1 zlGdBI)(~uPiWO_IQ(BY?#eoO>-(~{NHrJ1lkIMu&cbnJiVq6|)BTV4mFe!BndF5?g z4J2s_JU!+(O(f<9N=e;G5W-zRF;h6pPY#A*+Rig-0-X8T6Qax+70Y-y6j3?_Pxvk> zhB4-yy9@<T@whHdA@zZ{4L^82RJI3XcQ}<uM3WJR&vhRDGi^?{=Bg;k&MT}D%W6)! zA<E}cFOKrL<jbPykkU21L8DI(i1;%$Xed<4<uRE0+a0ruWLNBD(5#2yG=m1MZ0qfu z(!U)J8f;69pI+sw(^m+WTrt1}Ci^cLw`Zqw4a0V6=lb>A@~?}P!Z>DM7VhnE*l5z~ z0iir0(Hia5CA%E6Zx>_sXvSQ<KV4dVzQdumNoQS%`=dBQVZp4Dq})m3cmtLCM$Ld_ zSB^e?z8?&XR0C|Obn`Cy_l#*_+Qe}MWF)0wD$<McLI%LZmA0orUs}vaV$$pVQg9E5 z?ZBX;!|6T`WY)RT6u%kv47#E|t69KuvMMkAg)MWpP+q%ExWx-{`j-5~1@#b}HS?1s zyz!TIo-oEoqTy29lI9Pp9=&iX`86T6ayt@<h3`AkEG5mT=hC8y!2gnW?afUb%l>~p zg(B{8?827zp~r5Bl?o)OlT<iK0XY>aNx4?iN*1ghS*;8XMU`zHF<@Q+oRDy02w+~} zO>zPO@?8Rw?7#9A?sU)W?(AwMRv6#noSP!@K4xd8XL@>Odb)qzP2uJ>UtVFw1I_dm zkwUl7+U!G*RdBQi+!j#iTyD-^Zhm(aRiz8I!GmYdwk9A`2~BC?K<EH?X~XE8Tl#f> z^Y*dkkB3_~ZY}+O2pj^yy5LOf@^{S}$9q*#ww(a?E&%03vcW;k6I3;WMSN@i_Or)l z+h-m&FWmLitTU@brg`ixSzn?t`uFPcxf|4!BZB0^nG|LGPRsn@eJRZ~nV~iXP#651 z-=_M9WF*2_Iu}V_?+Uj}_ydrJV2OMa?Wyl!+a7Wk2v4Vudf?{HJZYV|^>pgkUyqKo zfBrd{B&|&@>AIlP<eF5(9m6b0Ws>TeGE0Z=EM5B%S>qb4Ho(SvmMt*C@NHVmEKUEr zdG0>++teJ(Zswb3kN*I*G@)tg2+4iv$`oo3=df>q-%vGAwc2(Dx0V*~HTNHEp2ZCW zw=*DV+n1-2XXl$gJSHmvv^@XDuKeZJ`Pt@y{k9?g70`z7pIv%%72UPxaPA=gZrL_% z>`21?{1)k9WVma#(A`H3d`MP*PA!su?m0pr>!qcPOFOA0J+~m=&wSH9exQAd<bK^g zuK1>P6wgnmAK=->WzZGl=7@1OG)VjOndXf}JkG$Tng*q==Rm~U%q(wcw2PQv!9DSu zn&GN;3tyc>O}UN9fn;L(1N77t%jFs$UEUGY<sKTLUq|!R16bc4Z6asvYF%c1z<B%$ zHqNxuTv$=8^vpr@LxBplK<NzUN$p?emam+|s)KgjZY5}@*3>0XPT4#`J9YVo-?2OA z7!6SM6L>I<6W#Q6>gs~Z?ioBP@l?#M6~NM5Wxvp57ibL~hJaR}AHre!si!lKnWYu! zVz%>L#D%HbMxK*!-ze286*6OTzNFpd1#eW(4%hJ1puKR39+3H+z?_-O(#vcRM<RT0 zi3tTbm}X3!0>Ma(3@Hu-XnFoF5qvgE+5#+1ZvI3h{L|^*o=%@?|MYO_+7WhqasE4Y z7PQ8(z2Xa$6*ZY6g8w2~BRpE3|7m&Z0=TnXd;*I+!sxRHczFj_sC$nfkJ1B48=P(W zFk^=9?c3Xkzuw65x2WW(f%S^78~mDw=m*+_wuPC|pb5>k_ujV5u~HsjX>cd0h-zwJ zyHzUdqaJ8$mKbApk(KF|(pNfsBdW2qvQ|oQNi5AQ>SP@WTWa?yA`X)edTXI_<EmND zGc6R^3#U_bH!LLO1l<iQ30Z+>2`Qx})4jA%8$NysTBv#`Efh2wB|*yVm?)<w@*XXe z8PoC;xOmj^HLEz5BSXfDn{UxOjdUUGJ_qP+ncOOAc4NRhw-L$G{r^on7V>$uTqo@) zzQlF}t0&5fj*(MT%jtiM4!t%yA`T2N?V5{*Pe#6*5~@K{4n=med;mF~WBTa&JzOEw zXv<E?0#~rxA?`Ex4Sg^~a$#MbCqtj~w;QX$VFt31_?SqBGI`6=ff3hx4a3-`Yv$n4 zy2$IX*CXrrmz$~;YkY7h67Y&<9`xq#)rMIQezmDoX;^xfms`MgFkcU@NXaltbvuDW z(nNtsuW_Ee-F;U!KM_wUNhPkxiKLXa%{;|;V^?G6<gUV3<J${g6}Ia}abkO8Bp0ji zEVu)w{L(VcdNU>?5_aP+A!7};jc&%cF(>)TNx^wWcj)Aym=d^CnBH>Uqbcy!4kZT< z9Y0yn#wJSjhGrxXGM+EQ(JHP9d8F`PAcT@)B3DWfAtb#tAr$e<j{(}86>^ZR(jkoF z>U0RhL?hX%nTJ?A=%hziIb5H*CzkQG?t%hNCRg<UBj8|C-mVlIW?Hp%<(&s+AsNcn zm2dc%*Of#6zUqgVQmIldBv$$SA|a{iR63mscYd+zebVdvqNbbmYDF_TT~fzonjkx_ z8%8w%Te@ZdN)h7nP$Mgk_aYo)Fp$Mswv?wK0Jt-MqgpJH9S&J8zyA(1)kRx9$BFo} z2wzskjAmN%x0e==HIL0R6=>uO4&u<>($I2v=am37n;L-7Q_poCR-prY&1t1<jYTT+ z`sR{pwgTaddBSShvQ{!8od6THvfGZfbFhK&FTAlX;4p(86J&f3>>BdaAw*wCiU1=E zS})c<T&-$o+F@sa`lf?;GW7t|c1~jldiK=EtEQD}SdM3?kC1t`%LWTNKt1zeces_v zx)=`~ANgH&d}SQ9Aq7w1x!6JX$O~6NTp^X3TZsRw=M*w;15lo}fg~O{<yhk-lP@Nl z^U&h|?m33=WU#=hI9udwc-gb4Cs|H2^hzGUeffu3RJ~hT*3cvMM!Bq+li>4zoq=`k zoq`A>kOHH}ylI(mo%gcTIJ)6FtVCX~!9=aIv-A#b+JNd}-?VPu=Wfnkac`EtKH=Up z&n~c=I4#&N-8E3EfpJpKsN~JKD8(htgU_bd>;E+|Dhz1l+IkCC9imG1%Yxwa3|Ne% zX?xpM=w6i4S5aiaASHZ7DQ;2EDNF6MELBl_WvL;_l7;??5|yOnElL)`7A5vql$0im zCvQ=rr>IDmvfwzV&$8sC!ZXIWW0V-8D5<}q668N9$~IQ)<o8~M$^8|Ulm*30mmHd| zM1Mu4#58h-ZSWYPgk*muqy-rn!Zw7o2U4#hQhgSo3djeURA?gl6G4)aPSNg9hc2N{ z$wSts*ykA~bP4^*8(B!Gw5!z@U}3R8i6e`0GUXeZLRiAY{=@*Yc=eR!lLPjg9_>+R ze*!@!LzHraD21l0KQSQ7YJ#$aD2E<8Vt?abPADQ}3Nf?*5c?AVgb~tI0Eo%ZMf4{C z<P>-bViBnq;qCr}ft*eV2_L_O77Jp3VnJ4<6yt%=azX4<E~ui+SU?S1gw&@rP*s(( zfD~E?Nc{<cgrLwxVnz}}4;HDvF`poFC0hi99`jOvV?H4z>7p<r$uGdkQh%dA0ctWY zCrhElfYhHDNF-#D@jz%XAoV8(5=l{EJP>*z(C1zx0sf`5h|oj6&pk*Yr3#D%LJ#>q zw;qXfnyvcy9mjLZ=yT7JP*QB&=h<q6<>WrM9SJol_|^cvZAZ7Fq<(iDNwS{x?GZzl z)StXbijw5tA%-ofKe0okfa>2MhApW-k&~3vq;G$i2wPHr0w+o4PM_K!8MdVU<W4e? zQhXed;)fOG`MAYG`^Hg^;~XLQz>%|7HLp#2RJD1{(#Qii&1gQj@4%M)^=P&YhAszf zw?Eg2q-biBpoL`_Ff=e`4CW`>#tK8_SiD*&aPdy)D{tT+7l((BBEW3PGEP(#QY;Mv z+^sx#8>Tq!7$FM4{q`XZz;feZ0wDJ<EIgf_Yt4O)fvSQd{5cBQ(CNd(OeSQGL357* zIcPEL>S_75u=H|GH#Mt)#)lro93dx~85#L=-(IGji+kQ1)t%ng9eG21sH~PP86{9< zkBt1y7bJ)zv5)AxcL()(2+<*4rozyUwldnt?{xLv)Gw8q>#-&WH*#q5Xr{-@U?5*! zYj0>3KQEi{TF&5D^V!2$Co7vZ-R7uK_^GJx`x<ESeQH&xmN&+5pg(ji%V5_7Dh930 z-o|lsKR1$>=MxN8k5Ro8viww?lU4TK3mL|~5hbl|nN<heiDtq)KAlEdo<7_>{8P6^ zDgaEnr5lEyab7)=yj{@Al9~3w^X(^xnh!b0Q<u(KwN$ZQY5FoLwcUBf;2gxUEDAf_ zlNh6lSaCa}en_-ejWlW&3HOfRa5}3?zr9RyEUjTSR!k4gg(HYQ@KAK{2ZtiOTrG-< zdbw07t&}2~3r7)s;Lf4P=9)K-BSp^TYEhKaDmfO{a0L|6TsVs819!Swc!1>4zpF)y zp;uQ*2+4w@!q8p##`cl@cBeR(t3}8}$t<pz6wHJp2Janmo;^6zymSxAaV}S_UrJe1 zTXD}oCVbz(y*p$sUTt5#NQaDlxmx7Zb5*leT5<odY&eqG6L-{{J3>bd{#`Y4v|`D! z$SkpPQO#w=ljS~j`|I{Th#7$WWnZqIh^v`o17TKHF6CH89zn;Rxuo5>W6!Y8<!X_p zec5;^9cSmmFB{k+Hz339`|XpzB3;hqYSC3I)pILf3DI2mRV+SmC|bVpJ(5HJ24fm} zd?7kFCiQBrqycno%|m{@2nIsmVn|*Z4~WIYK=-0!Xt%Za?BT`MFVh%KVU~wa^>4}0 z?u%L>xmo~w3EsK9HfmWe;D?Lm1+G-+VFFbPc<kL-+ZI8V=G)2DtsEB_!ygco1_0<1 zBy^46n9W!5agnRIYb4jabE$RXzNh+KUk2}QJ4}Wqx0mt(_IsXiHL&q_&62*wed_`m zd$I6h;33r8^)Z<w((Os}{!xt12=3=BkOuflp=;grA?$)%t}|rKmqjY`d>?DAk+b47 zj`d_V8yD0uQ4mvOg220$+jt26aGJJGrIrbfaFk4E81lm2`R0@JWVgONeG2^1z!vP9 z6AK1hZl}<D2{NV-romAaX|9SV{4CSIHTPek`(`ktJ3?K?g8Y?wSc8Ew#_xL+-~#5R z9d|uDx7T4W-cx;oZeg6MGUXX3vjB~a$2=Vq`E+ot-)=SM9yU*0S$gu&!4b~@vtZ%2 zxutC-Dg_x!duVJJWop?Rbt_ibiEtDU_&!qBt??>uJFIA=qLp?0WCY$xECRWE>*l$I zhP)@4lyn0kl%p0*FY1+ihE|m#36jpC4}&X}^#^iv-fXnS0VDC3XHWiUpT5>S_HXQV z!XCGpzs-;)YM*-8`qzo(aqtRl?!VML^n;DC>0)bwpJ~?2tg+RP=$MU_qYVsB$P9IN z&hF^bwpZ;L^iu8Kp5=U<ESH}g{_D~AWB`alN)TiTxPEGmb|SrQ?!VDG`*Z8;6#!<0 zF#)5!SH5*!GZ7lSRH-$1tm$ZHQj!o1SUue|@?}FeCrM|OwOxi@DS|dv1f}_eW;94v zB<F0yvZ@vDSM-?_TnGl&Y+XEy_=z-S#wShVE+f1Nvv9e~Xw3TJ^Hi#p_lZcOzLY+= zHT)nh%Az1s=(V^Qmz*gbBpbeRRud3S9Act)vCGGew=@QEFQgdwx+IN|UG@&WG-5!M z-ltQ?d9dZDQzu&Uw_8`i2e^Ie0%4iuGslsGZZ04CuC=fap8R^OHGigk{J@`6i+??u zrB3JVU*@`k<Oh)>0YN-D91c6;;Ky>~B|Y2N(J-qXn!eQHq?_xYFC3#O<6nxBoKV%2 zEK8Ii7JLUoT@iw8Ct5(H{nwpm4<Q&yYyRt{JA@(UmZ!d_F1gDW?h;a(cMju7=n@Jz zh!i=la%ZERqK(y(CN8DnNIcH!d6D9J(t0-Uo+sm_dGXrv-lH&qfUomDaA*b#_nUT@ zh)`8Z^jW1O`7)?Vtpk@S)-PKpL1f`;Xl0F)+YG`X?EM-RAP3I0&n`6Yoc`<akL@Q% zW3iY|ne8G*W{dAo$jG1&oD?;(8+F#WUe@D+KrdXwtP!CXwV0SjxFv(J<I@2{vqt4) zER~cLK^-uvl~GYvV`(`pstS?Bz##iv^fAQ6UHaoh>);ejPV?87=Exr4TQ3BocRj?= z)4LD_DSB036GiA9%DT&YA3L(!S+I1-8<1_7(MT2Im}!%aaI^1yD|$MOt_g<s=NDa* z+qGRePxY#$x2)0a1NG5KvWFSd%Jng`oHxcAYpjAYP#R?$8z|73tHGtIx1-t)%d8XD zq>gHmm`tac4~vvmsSk^oNHJV4MOD;Zj%u4XtlUve*%ZQ2O;F@?TBRfiRyAie$QT2! z<G1XyuAMtQ#ICk07hh}Fm8)RZ@>Sbyh)=?~4TWfO+ihs`2G?zfw!o|I%2k<YBzLT? zE0>fO5=6idMhoZ4wd#Em<jU0x3w86`AOBy1q2_|Z6eLn&YKH^Tnd<@4NoxBagN9m9 z@6ZKfRk=?jt2~u3pdy6}Q}AL9-OD?WtSrpQo#VP028INc04J$0pEu5DFZ2<Agb}g? z%=}~PRs>EV%a@<XEUh&Fmj&=tPUAM+4&2K04mVrJA2xrx)w;T{wD)oA)~TgOd%Nw_ zB~=Azo>?1Y?IbK&rViT9hiKO^bGSAC0Cs5@gqfkk*a%+jgR>4yNq2QXDw*Nq4P%tH z@99Ez`NAVt4=~d!`iP@4Bm2~f9wr932VN2&eNNa;ZilcgEU339#!D|u7!OT?n0fZw zA}TGQgO@KXAY{>k6D*qHF+#IGLU+P@5K@>dtxnuSRpFthGmD61cKO-klgn35dK78C zvJgKBR?!fw?`HGyqvoZ1447zY4&n|RpG9ODz-8jz-dv`;W<c)o;+FMB_5yt!Xa)z0 z)_VBA7o?XDZ0~u1GKFf@>gc@Q4y2u)qXkedQaqg2;>^-waCBx&I6+pJhfXiu`?`7a zSAKaJkQM^VyK&=}?*9Dj_usoGE|#DQ)2&N4md?*XIPwE`+t<%^O%E|ZXt*94QrZTi z0?3}OAB}^P?@>_7`K#`jG4$7N7GdS)>{XKC(&Dl9{e4|auIsrdDh2@`?-K5g4lqsk z^v$KIy=3P}q(axvYZFXEeiet<aokuv!rkjNdfoNYd<lYJ9D(M4czF5Tem~7xK5y#v zdS+v_HfbVC#o)%F2<*9OFy+Vx5KT3*v1-<;CdF2Xylohf&5*Vp*{s)fb3)I<K91$o zfh+IIeVkeO=wP{eorMKkOPbuG;PDX_>WfUkk#T8vjbvP^6h{obV7-y7?OMNQZDSl1 zDWGn*hS!4r<!$uA9`<B1o+lY)vsPtzZ9$u;nndzIT}wGV64O}vdLtWIYpeW*=@lt6 z2D;7R7=}vNZO28xun2oRX$l4BEe)5mhlR}oIx`-{qhBp;#Xu{~uMx~rnNvU`)C05A z3>j68D#p4+RikfdR5dqctU`V_X}buaqYD4LzIB(iwOkwhpTVfDMH`BthKx^@|Jc;Y z1$-@dzJ3>U2LC{9X_S8v9K?-2{ija4b9Xhn{SEhnJ)X+)f!cuLLTzK!sG44KX=eCh zFq$(=LN7~4zaPZ7Y=kbV{2m(G4O!vMCm(L|=0$eMPCEGNH8-+a&7c9;=;vchcQ<P1 zo6SdQ9jhP0?FQ15quV}t2QYLt>qVg6{=}c`(`}=>KYhc!kKwQ!;U2(97~aMw$yP0^ zTISP`)!-hQ@kewo4>jxD@KdU|fN}N0zyN<Y@n%vGNE<=jN%L(1&K|V$9FF2zI6BPN zKKALlXbrfm%O`Z%?E7Y0?{CVQSu9lmwKX~%9f__B`gRZP2LNoco0bcwSKlzXRVxBV z4n{TBHJJFrQT!95s^Ikn!ZrA=L^yh^^d?P1uGX#dH3Gi*Y^?OfFqtk0cafT{RaKQ# zgfuCP)oLN~oQeRF?a*h!7q5**8<o6H=3$+%&F+w2#llzxV^(ANM!7tR(j|Q5#^o2N z8~Z3$BJ-#D_pO^g9F2b7NXbI_^F}hOYM(b`A(_VC0(p{_1^CSu;7Lk{Uo8Q@c@=&O zGW_Ngl7o;-l9Y-p!jnYq^M<0N$n&%+(>#;#tK{G}jrk<i=pK7a$FlF}1>~4f(S3-* zfLsg?(ShQGPYINsPCzvxknHOh3rXo_<qt{eW(-jnNt=#F^Z6+MI4Dr1JLh>TUfoDv zY^d&n<e$77BbAKivUKj#41+Li&M4(}M2DS_lp65(yD?Iu8cJ~TAgCZBU5Z6R=L!&p zch_k<5PIk=8y&8HRHIKSRkKV)=$@fHofXmZC2O^6Lh}x+22LN;76SXqH)kS-66tr> zntEuxCwb4A>^+*&?thB#=rDkt;~syO^$$sVV7E7nsPA2VW5aykF;Mlrto{>^u5>a# z4@L*0>s$fO(v87_W?gisg9_v*dd=okW=$VF6}lcAL+jmy+)!E{9O%@uiol>mgBZ|M z7+Sw39_K@iqT08o);F7*5DRIAYJFuHA_aqLOOVB+kOXyCDlMf_h-Q>ZlMR0_t?zpu zeY90wx!zX|15hT2>4cP~_>!n8>!DbCZ;K$JVhwd_y_0Sh__So-ZIz9_^l&w%hs%?G z0eK$V32WuSC}a^`7wwV@u4Y4`)8*>9xFiTEL6H(*n<!PYg|Vzmme920X(<`PRGd<- z4+4hpckuQ8lx%Xt(6ol7tz5|_I!d+=I#j%3N|90m;lwVwwbzH%`^c#yIS8-#N~<U< zh04TsedVnucW*seR8>(RIVYc|q6|<ovT;)rlvqK61#zre+`-cv$yLiW*t6>S$kv8V kwz`#wDE;q%$jaE*%D_OuD>www69#200JE0%0tZ(C0H>nZ&Hw-a literal 0 HcmV?d00001 From 89d9677ff905e2b19cc70f8875f64a340e0f5aae Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 1 Mar 2020 17:43:16 -0800 Subject: [PATCH 429/774] Updated intro para. --- README.md | 3 +-- pom.xml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cfb3a1d957..7dada4fb16 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # jsoup: Java HTML Parser -**jsoup** is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. - +**jsoup** is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. **jsoup** implements the [WHATWG HTML5](https://html.spec.whatwg.org/multipage/) specification, and parses HTML to the same DOM as modern browsers do. diff --git a/pom.xml b/pom.xml index 7e4dea7f73..8f453a9189 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.13.2-SNAPSHOT</version> - <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> + <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> <issueManagement> From 140b48a58568c9614ad91773598b56891bb70bac Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 1 Mar 2020 18:17:24 -0800 Subject: [PATCH 430/774] Help maven to compile incrementally --- pom.xml | 6 ++++++ src/main/java/org/jsoup/package-info.java | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8f453a9189..19034a2f4b 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,12 @@ <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> + <compilerArgs> + <!-- saves output for package-info.java, so mvn sees it has completed it, so incremental compile works --> + <arg>-Xpkginfo:always</arg> + </compilerArgs> + <!-- this means incremental = true... --> + <useIncrementalCompilation>false</useIncrementalCompilation> </configuration> </plugin> <plugin> diff --git a/src/main/java/org/jsoup/package-info.java b/src/main/java/org/jsoup/package-info.java index 49526116b4..5c5696596a 100644 --- a/src/main/java/org/jsoup/package-info.java +++ b/src/main/java/org/jsoup/package-info.java @@ -1,4 +1,4 @@ /** - Contains the main {@link org.jsoup.Jsoup} class, which provides convenient static access to the jsoup functionality. + Contains the main {@link org.jsoup.Jsoup} class, which provides convenient static access to the jsoup functionality. */ -package org.jsoup; \ No newline at end of file +package org.jsoup; From 8ddcef3c420f64689ae486237291daf975940973 Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Mon, 2 Mar 2020 17:10:20 +0100 Subject: [PATCH 431/774] Test improvements; assertions updated and unused exception declarations removed. --- .../java/org/jsoup/integration/ConnectIT.java | 6 +- .../org/jsoup/integration/ConnectTest.java | 2 +- .../java/org/jsoup/integration/ParseTest.java | 2 +- .../org/jsoup/integration/UrlConnectTest.java | 19 ++- .../integration/servlets/Deflateservlet.java | 2 +- .../integration/servlets/FileServlet.java | 3 +- .../integration/servlets/HelloServlet.java | 2 +- .../servlets/InterruptedServlet.java | 3 +- .../integration/servlets/RedirectServlet.java | 4 +- .../jsoup/integration/servlets/SlowRider.java | 2 +- .../org/jsoup/internal/StringUtilTest.java | 7 +- .../java/org/jsoup/nodes/AttributeTest.java | 2 +- .../java/org/jsoup/nodes/DocumentTest.java | 104 +++++++-------- .../java/org/jsoup/nodes/ElementTest.java | 126 +++++++++--------- .../java/org/jsoup/nodes/EntitiesTest.java | 9 +- .../java/org/jsoup/nodes/FormElementTest.java | 2 +- src/test/java/org/jsoup/nodes/NodeTest.java | 24 ++-- .../java/org/jsoup/nodes/TextNodeTest.java | 8 +- .../org/jsoup/parser/AttributeParseTest.java | 2 +- .../org/jsoup/parser/CharacterReaderTest.java | 8 +- .../java/org/jsoup/parser/HtmlParserTest.java | 2 +- .../org/jsoup/parser/ParserSettingsTest.java | 4 +- .../java/org/jsoup/parser/TokenQueueTest.java | 4 +- .../org/jsoup/parser/XmlTreeBuilderTest.java | 2 +- .../java/org/jsoup/select/ElementsTest.java | 4 +- 25 files changed, 174 insertions(+), 179 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java index 4a7336d566..19bfb8f760 100644 --- a/src/test/java/org/jsoup/integration/ConnectIT.java +++ b/src/test/java/org/jsoup/integration/ConnectIT.java @@ -19,7 +19,7 @@ public class ConnectIT { // Slow Rider tests. Ignored by default so tests don't take aaages @Test - public void canInterruptBodyStringRead() throws IOException, InterruptedException { + public void canInterruptBodyStringRead() throws InterruptedException { // todo - implement in interruptable channels, so it's immediate final String[] body = new String[1]; Thread runner = new Thread(new Runnable() { @@ -47,7 +47,7 @@ public void run() { } @Test - public void canInterruptDocumentRead() throws IOException, InterruptedException { + public void canInterruptDocumentRead() throws InterruptedException { // todo - implement in interruptable channels, so it's immediate final String[] body = new String[1]; Thread runner = new Thread(new Runnable() { @@ -70,7 +70,7 @@ public void run() { assertTrue(runner.isInterrupted()); runner.join(); - assertTrue(body[0].length() == 0); // doesn't ready a failed doc + assertEquals(0, body[0].length()); // doesn't ready a failed doc } @Test diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 822ee6bca2..7b612ddd71 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -373,7 +373,7 @@ public void handlesWrongContentLengthDuringBufferedRead() throws IOException { assertEquals(HelloServlet.Url, doc.location()); } - @Test public void handlesEmptyRedirect() throws IOException { + @Test public void handlesEmptyRedirect() { boolean threw = false; try { Connection.Response res = Jsoup.connect(RedirectServlet.Url) diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index 313a091fd6..7e8c8f1de6 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -131,7 +131,7 @@ public void testHtml5Charset() throws IOException { in = getFile("/htmltests/meta-charset-2.html"); // doc = Jsoup.parse(in, null, "http://example.com"); // gb2312, no charset assertEquals("UTF-8", doc.outputSettings().charset().displayName()); - assertFalse("新".equals(doc.text())); + assertNotEquals("新", doc.text()); // confirm fallback to utf8 in = getFile("/htmltests/meta-charset-3.html"); diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 1e69d89c74..e27388a70a 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -1,7 +1,6 @@ package org.jsoup.integration; import org.jsoup.Connection; -import org.jsoup.HttpStatusException; import org.jsoup.Jsoup; import org.jsoup.UnsupportedMimeTypeException; import org.jsoup.internal.StringUtil; @@ -23,8 +22,7 @@ import java.net.URL; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** Tests the URL connection. Not enabled by default, so tests don't require network connection. @@ -47,7 +45,7 @@ public void fetchBaidu() throws IOException { assert(res.hasCookie("BAIDUID")); assertEquals("text/html;charset=gbk", res.contentType()); } - + @Test public void exceptOnUnknownContentType() { String url = "http://direct.jsoup.org/rez/osi_logo.png"; // not text/* but image/png, should throw @@ -227,7 +225,7 @@ public void handlesDodgyCharset() throws IOException { String url = "http://direct.infohound.net/tools/bad-charset.pl"; Connection.Response res = Jsoup.connect(url).execute(); assertEquals("text/html; charset=UFT8", res.header("Content-Type")); // from the header - assertEquals(null, res.charset()); // tried to get from header, not supported, so returns null + assertNull(res.charset()); // tried to get from header, not supported, so returns null Document doc = res.parse(); // would throw an error if charset unsupported assertTrue(doc.text().contains("Hello!")); assertEquals("UTF-8", res.charset()); // set from default on parse @@ -276,8 +274,7 @@ public void testUnsafeFail() throws Exception { */ @Test(expected = IOException.class) public void testSNIFail() throws Exception { - String url = WEBSITE_WITH_SNI; - Jsoup.connect(url).execute(); + Jsoup.connect(WEBSITE_WITH_SNI).execute(); } @Test @@ -443,7 +440,7 @@ public void fetchViaHttpProxySetByArgument() throws IOException { } @Test - public void invalidProxyFails() throws IOException { + public void invalidProxyFails() { boolean caught = false; String url = "https://jsoup.org"; try { @@ -486,7 +483,7 @@ public void canSpecifyResponseCharset() throws IOException { // included in meta Connection.Response res1 = Jsoup.connect(charsetUrl).execute(); - assertEquals(null, res1.charset()); // not set in headers + assertNull(res1.charset()); // not set in headers final Document doc1 = res1.parse(); assertEquals("windows-1252", doc1.charset().displayName()); // but determined at parse time assertEquals("Cost is €100", doc1.select("p").text()); @@ -494,7 +491,7 @@ public void canSpecifyResponseCharset() throws IOException { // no meta, no override Connection.Response res2 = Jsoup.connect(noCharsetUrl).execute(); - assertEquals(null, res2.charset()); // not set in headers + assertNull(res2.charset()); // not set in headers final Document doc2 = res2.parse(); assertEquals("UTF-8", doc2.charset().displayName()); // so defaults to utf-8 assertEquals("Cost is �100", doc2.select("p").text()); @@ -502,7 +499,7 @@ public void canSpecifyResponseCharset() throws IOException { // no meta, let's override Connection.Response res3 = Jsoup.connect(noCharsetUrl).execute(); - assertEquals(null, res3.charset()); // not set in headers + assertNull(res3.charset()); // not set in headers res3.charset("windows-1252"); assertEquals("windows-1252", res3.charset()); // read back final Document doc3 = res3.parse(); diff --git a/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java b/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java index eaf4cfb071..f131d6609d 100644 --- a/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java +++ b/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java @@ -14,7 +14,7 @@ public class Deflateservlet extends BaseServlet { public static final String Url = TestServer.map(Deflateservlet.class); @Override - protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { res.setContentType(TextHtml); res.setStatus(HttpServletResponse.SC_OK); res.setHeader("Content-Encoding", "deflate"); diff --git a/src/test/java/org/jsoup/integration/servlets/FileServlet.java b/src/test/java/org/jsoup/integration/servlets/FileServlet.java index 4a595796f0..399c2e7afd 100644 --- a/src/test/java/org/jsoup/integration/servlets/FileServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/FileServlet.java @@ -15,7 +15,6 @@ public class FileServlet extends BaseServlet { public static final String Url = TestServer.map(FileServlet.class); public static final String ContentTypeParam = "contentType"; - public static final String LocationParam = "loc"; public static final String DefaultType = "text/html"; @Override @@ -43,7 +42,7 @@ public static String urlTo(String path) { } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException { doGet(req, res); } } diff --git a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java index 0660d1ad6f..e680089422 100644 --- a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java @@ -11,7 +11,7 @@ public class HelloServlet extends BaseServlet { public static final String Url = TestServer.map(HelloServlet.class); @Override - protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { res.setContentType(TextHtml); res.setStatus(HttpServletResponse.SC_OK); diff --git a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java index 298aaf1589..22180e13d5 100644 --- a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java @@ -3,7 +3,6 @@ import org.jsoup.integration.TestServer; import org.jsoup.parser.CharacterReaderTest; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -15,7 +14,7 @@ public class InterruptedServlet extends BaseServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { String magnitude = req.getParameter(Magnitude); magnitude = magnitude == null ? "" : magnitude; res.setContentType(TextHtml); diff --git a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java index a6784c4853..20084c49ce 100644 --- a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java @@ -16,7 +16,7 @@ public class RedirectServlet extends BaseServlet { private static final int DefaultCode = HttpServletResponse.SC_MOVED_TEMPORARILY; @Override - protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse res) { String location = req.getParameter(LocationParam); if (location == null) location = ""; @@ -36,7 +36,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException { doGet(req, res); } } diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java index 5e5b550627..bccd8a97d4 100644 --- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -25,7 +25,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE int maxTime = -1; String maxTimeP = req.getParameter(MaxTimeParam); if (maxTimeP != null) { - maxTime = Integer.valueOf(maxTimeP); + maxTime = Integer.parseInt(maxTimeP); } long startTime = System.currentTimeMillis(); diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index d9b9740e00..ca39f60efc 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import java.util.Arrays; +import java.util.Collections; import static org.jsoup.internal.StringUtil.*; import static org.junit.Assert.assertEquals; @@ -14,8 +15,8 @@ public class StringUtilTest { @Test public void join() { - assertEquals("", StringUtil.join(Arrays.asList(""), " ")); - assertEquals("one", StringUtil.join(Arrays.asList("one"), " ")); + assertEquals("", StringUtil.join(Collections.singletonList(""), " ")); + assertEquals("one", StringUtil.join(Collections.singletonList("one"), " ")); assertEquals("one two three", StringUtil.join(Arrays.asList("one", "two", "three"), " ")); } @@ -62,7 +63,7 @@ public class StringUtilTest { assertTrue(StringUtil.isWhitespace('\r')); assertTrue(StringUtil.isWhitespace('\f')); assertTrue(StringUtil.isWhitespace(' ')); - + assertFalse(StringUtil.isWhitespace('\u00a0')); assertFalse(StringUtil.isWhitespace('\u2000')); assertFalse(StringUtil.isWhitespace('\u3000')); diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 2c064f6b95..c8edfd0928 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -47,7 +47,7 @@ public class AttributeTest { assertEquals("two", oldVal); assertEquals("three", attr.getKey()); assertEquals("four", attr.getValue()); - assertEquals(null, attr.parent); + assertNull(attr.parent); } @Test public void hasValue() { diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index c496a4376f..e6eab62262 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -26,8 +26,8 @@ public class DocumentTest { private static final String charsetUtf8 = "UTF-8"; private static final String charsetIso8859 = "ISO-8859-1"; - - + + @Test public void setTextPreservesDocumentStructure() { Document doc = Jsoup.parse("<p>Hello</p>"); doc.text("Replaced"); @@ -35,16 +35,16 @@ public class DocumentTest { assertEquals("Replaced", doc.body().text()); assertEquals(1, doc.select("head").size()); } - + @Test public void testTitles() { Document noTitle = Jsoup.parse("<p>Hello</p>"); Document withTitle = Jsoup.parse("<title>First</title><title>Ignore</title><p>Hello</p>"); - + assertEquals("", noTitle.title()); noTitle.title("Hello"); assertEquals("Hello", noTitle.title()); assertEquals("Hello", noTitle.select("title").first().text()); - + assertEquals("First", withTitle.title()); withTitle.title("Hello"); assertEquals("Hello", withTitle.title()); @@ -98,7 +98,7 @@ public class DocumentTest { assertEquals("<!doctype html><html><head><title>Doctype test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", TextUtil.stripNewlines(clone.html())); } - + @Test public void testLocation() throws IOException { File in = ParseTest.getFile("/htmltests/yahoo-jp.html.gz"); Document doc = Jsoup.parse(in, "UTF-8", "http://www.yahoo.co.jp/index.html"); @@ -141,12 +141,12 @@ public class DocumentTest { Document doc = Jsoup.parse("x"); assertEquals(Syntax.html, doc.outputSettings().syntax()); } - + @Test public void testHtmlAppendable() { String htmlContent = "<html><head><title>Hello</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p><p>Two</p></body></html>"; Document document = Jsoup.parse(htmlContent); OutputSettings outputSettings = new OutputSettings(); - + outputSettings.prettyPrint(false); document.outputSettings(outputSettings); assertEquals(htmlContent, document.html(new StringWriter()).toString()); @@ -165,18 +165,18 @@ public class DocumentTest { doc.clone(); } - @Test public void DocumentsWithSameContentAreEqual() throws Exception { + @Test public void DocumentsWithSameContentAreEqual() { Document docA = Jsoup.parse("<div/>One"); Document docB = Jsoup.parse("<div/>One"); Document docC = Jsoup.parse("<div/>Two"); - assertFalse(docA.equals(docB)); - assertTrue(docA.equals(docA)); + assertNotEquals(docA, docB); + assertEquals(docA, docA); assertEquals(docA.hashCode(), docA.hashCode()); - assertFalse(docA.hashCode() == docC.hashCode()); + assertNotEquals(docA.hashCode(), docC.hashCode()); } - @Test public void DocumentsWithSameContentAreVerifiable() throws Exception { + @Test public void DocumentsWithSameContentAreVerifiable() { Document docA = Jsoup.parse("<div/>One"); Document docB = Jsoup.parse("<div/>One"); Document docC = Jsoup.parse("<div/>Two"); @@ -184,13 +184,13 @@ public class DocumentTest { assertTrue(docA.hasSameValue(docB)); assertFalse(docA.hasSameValue(docC)); } - + @Test public void testMetaCharsetUpdateUtf8() { final Document doc = createHtmlDocument("changeThis"); doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetUtf8)); - + final String htmlCharsetUTF8 = "<html>\n" + " <head>\n" + " <meta charset=\"" + charsetUtf8 + "\">\n" + @@ -198,19 +198,19 @@ public void testMetaCharsetUpdateUtf8() { " <body></body>\n" + "</html>"; assertEquals(htmlCharsetUTF8, doc.toString()); - + Element selectedElement = doc.select("meta[charset]").first(); assertEquals(charsetUtf8, doc.charset().name()); assertEquals(charsetUtf8, selectedElement.attr("charset")); assertEquals(doc.charset(), doc.outputSettings().charset()); } - + @Test public void testMetaCharsetUpdateIso8859() { final Document doc = createHtmlDocument("changeThis"); doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetIso8859)); - + final String htmlCharsetISO = "<html>\n" + " <head>\n" + " <meta charset=\"" + charsetIso8859 + "\">\n" + @@ -218,34 +218,34 @@ public void testMetaCharsetUpdateIso8859() { " <body></body>\n" + "</html>"; assertEquals(htmlCharsetISO, doc.toString()); - + Element selectedElement = doc.select("meta[charset]").first(); assertEquals(charsetIso8859, doc.charset().name()); assertEquals(charsetIso8859, selectedElement.attr("charset")); assertEquals(doc.charset(), doc.outputSettings().charset()); } - + @Test public void testMetaCharsetUpdateNoCharset() { final Document docNoCharset = Document.createShell(""); docNoCharset.updateMetaCharsetElement(true); docNoCharset.charset(Charset.forName(charsetUtf8)); - + assertEquals(charsetUtf8, docNoCharset.select("meta[charset]").first().attr("charset")); - + final String htmlCharsetUTF8 = "<html>\n" + " <head>\n" + " <meta charset=\"" + charsetUtf8 + "\">\n" + " <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + " <body></body>\n" + "</html>"; - assertEquals(htmlCharsetUTF8, docNoCharset.toString()); + assertEquals(htmlCharsetUTF8, docNoCharset.toString()); } - + @Test public void testMetaCharsetUpdateDisabled() { final Document docDisabled = Document.createShell(""); - + final String htmlNoCharset = "<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + " <body></body>\n" + @@ -253,11 +253,11 @@ public void testMetaCharsetUpdateDisabled() { assertEquals(htmlNoCharset, docDisabled.toString()); assertNull(docDisabled.select("meta[charset]").first()); } - + @Test public void testMetaCharsetUpdateDisabledNoChanges() { final Document doc = createHtmlDocument("dontTouch"); - + final String htmlCharset = "<html>\n" + " <head>\n" + " <meta charset=\"dontTouch\">\n" + @@ -266,48 +266,48 @@ public void testMetaCharsetUpdateDisabledNoChanges() { " <body></body>\n" + "</html>"; assertEquals(htmlCharset, doc.toString()); - + Element selectedElement = doc.select("meta[charset]").first(); assertNotNull(selectedElement); assertEquals("dontTouch", selectedElement.attr("charset")); - + selectedElement = doc.select("meta[name=charset]").first(); assertNotNull(selectedElement); assertEquals("dontTouch", selectedElement.attr("content")); } - + @Test public void testMetaCharsetUpdateEnabledAfterCharsetChange() { final Document doc = createHtmlDocument("dontTouch"); doc.charset(Charset.forName(charsetUtf8)); - + Element selectedElement = doc.select("meta[charset]").first(); assertEquals(charsetUtf8, selectedElement.attr("charset")); assertTrue(doc.select("meta[name=charset]").isEmpty()); } - + @Test public void testMetaCharsetUpdateCleanup() { final Document doc = createHtmlDocument("dontTouch"); doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetUtf8)); - + final String htmlCharsetUTF8 = "<html>\n" + " <head>\n" + " <meta charset=\"" + charsetUtf8 + "\">\n" + " <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + " <body></body>\n" + "</html>"; - + assertEquals(htmlCharsetUTF8, doc.toString()); } - + @Test public void testMetaCharsetUpdateXmlUtf8() { final Document doc = createXmlDocument("1.0", "changeThis", true); doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetUtf8)); - + final String xmlCharsetUTF8 = "<?xml version=\"1.0\" encoding=\"" + charsetUtf8 + "\"?>\n" + "<root>\n" + " node\n" + @@ -319,45 +319,45 @@ public void testMetaCharsetUpdateXmlUtf8() { assertEquals(charsetUtf8, selectedNode.attr("encoding")); assertEquals(doc.charset(), doc.outputSettings().charset()); } - + @Test public void testMetaCharsetUpdateXmlIso8859() { final Document doc = createXmlDocument("1.0", "changeThis", true); doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetIso8859)); - + final String xmlCharsetISO = "<?xml version=\"1.0\" encoding=\"" + charsetIso8859 + "\"?>\n" + "<root>\n" + " node\n" + "</root>"; assertEquals(xmlCharsetISO, doc.toString()); - + XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0); assertEquals(charsetIso8859, doc.charset().name()); assertEquals(charsetIso8859, selectedNode.attr("encoding")); assertEquals(doc.charset(), doc.outputSettings().charset()); } - + @Test public void testMetaCharsetUpdateXmlNoCharset() { final Document doc = createXmlDocument("1.0", "none", false); doc.updateMetaCharsetElement(true); doc.charset(Charset.forName(charsetUtf8)); - + final String xmlCharsetUTF8 = "<?xml version=\"1.0\" encoding=\"" + charsetUtf8 + "\"?>\n" + "<root>\n" + " node\n" + "</root>"; assertEquals(xmlCharsetUTF8, doc.toString()); - + XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0); assertEquals(charsetUtf8, selectedNode.attr("encoding")); } - + @Test public void testMetaCharsetUpdateXmlDisabled() { final Document doc = createXmlDocument("none", "none", false); - + final String xmlNoCharset = "<root>\n" + " node\n" + "</root>"; @@ -367,44 +367,44 @@ public void testMetaCharsetUpdateXmlDisabled() { @Test public void testMetaCharsetUpdateXmlDisabledNoChanges() { final Document doc = createXmlDocument("dontTouch", "dontTouch", true); - + final String xmlCharset = "<?xml version=\"dontTouch\" encoding=\"dontTouch\"?>\n" + "<root>\n" + " node\n" + "</root>"; assertEquals(xmlCharset, doc.toString()); - + XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0); assertEquals("dontTouch", selectedNode.attr("encoding")); assertEquals("dontTouch", selectedNode.attr("version")); } - + @Test public void testMetaCharsetUpdatedDisabledPerDefault() { final Document doc = createHtmlDocument("none"); assertFalse(doc.updateMetaCharsetElement()); } - + private Document createHtmlDocument(String charset) { final Document doc = Document.createShell(""); doc.head().appendElement("meta").attr("charset", charset); doc.head().appendElement("meta").attr("name", "charset").attr("content", charset); - + return doc; } - + private Document createXmlDocument(String version, String charset, boolean addDecl) { final Document doc = new Document(""); doc.appendElement("root").text("node"); doc.outputSettings().syntax(Syntax.xml); - + if(addDecl) { XmlDeclaration decl = new XmlDeclaration("xml", false); decl.attr("version", version); decl.attr("encoding", charset); doc.prependChild(decl); } - + return doc; } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 3b833a3ca8..88a412971d 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -49,7 +49,7 @@ public class ElementTest { List<Element> empty = doc.getElementsByTag("wtf"); assertEquals(0, empty.size()); } - + @Test public void getNamespacedElementsByTag() { Document doc = Jsoup.parse("<div><abc:def id=1>Hello</abc:def></div>"); Elements els = doc.getElementsByTag("abc:def"); @@ -70,7 +70,7 @@ public class ElementTest { Element span = div2.child(0).getElementById("2"); // called from <p> context should be span assertEquals("span", span.tagName()); } - + @Test public void testGetText() { Document doc = Jsoup.parse(reference); assertEquals("Hello Another element", doc.text()); @@ -164,21 +164,21 @@ public class ElementTest { assertEquals("body", parents.get(2).tagName()); assertEquals("html", parents.get(3).tagName()); } - + @Test public void testElementSiblingIndex() { Document doc = Jsoup.parse("<div><p>One</p>...<p>Two</p>...<p>Three</p>"); Elements ps = doc.select("p"); - assertTrue(0 == ps.get(0).elementSiblingIndex()); - assertTrue(1 == ps.get(1).elementSiblingIndex()); - assertTrue(2 == ps.get(2).elementSiblingIndex()); + assertEquals(0, ps.get(0).elementSiblingIndex()); + assertEquals(1, ps.get(1).elementSiblingIndex()); + assertEquals(2, ps.get(2).elementSiblingIndex()); } @Test public void testElementSiblingIndexSameContent() { Document doc = Jsoup.parse("<div><p>One</p>...<p>One</p>...<p>One</p>"); Elements ps = doc.select("p"); - assertTrue(0 == ps.get(0).elementSiblingIndex()); - assertTrue(1 == ps.get(1).elementSiblingIndex()); - assertTrue(2 == ps.get(2).elementSiblingIndex()); + assertEquals(0, ps.get(0).elementSiblingIndex()); + assertEquals(1, ps.get(1).elementSiblingIndex()); + assertEquals(2, ps.get(2).elementSiblingIndex()); } @Test public void testGetElementsWithClass() { @@ -225,7 +225,7 @@ public class ElementTest { List<Element> none = doc.getElementsByAttributeValue("style", "none"); assertEquals(0, none.size()); } - + @Test public void testClassDomMethods() { Document doc = Jsoup.parse("<div><span class=' mellow yellow '>Hello <b>Yellow</b></span></div>"); List<Element> els = doc.getElementsByAttribute("class"); @@ -243,62 +243,62 @@ public class ElementTest { assertEquals(0, classes.size()); assertFalse(doc.hasClass("mellow")); } - + @Test public void testHasClassDomMethods() { Tag tag = Tag.valueOf("a"); Attributes attribs = new Attributes(); Element el = new Element(tag, "", attribs); - + attribs.put("class", "toto"); boolean hasClass = el.hasClass("toto"); assertTrue(hasClass); - + attribs.put("class", " toto"); hasClass = el.hasClass("toto"); assertTrue(hasClass); - + attribs.put("class", "toto "); hasClass = el.hasClass("toto"); assertTrue(hasClass); - + attribs.put("class", "\ttoto "); hasClass = el.hasClass("toto"); assertTrue(hasClass); - + attribs.put("class", " toto "); hasClass = el.hasClass("toto"); assertTrue(hasClass); - + attribs.put("class", "ab"); hasClass = el.hasClass("toto"); assertFalse(hasClass); - + attribs.put("class", " "); hasClass = el.hasClass("toto"); assertFalse(hasClass); - + attribs.put("class", "tototo"); hasClass = el.hasClass("toto"); assertFalse(hasClass); - + attribs.put("class", "raulpismuth "); hasClass = el.hasClass("raulpismuth"); assertTrue(hasClass); - + attribs.put("class", " abcd raulpismuth efgh "); hasClass = el.hasClass("raulpismuth"); assertTrue(hasClass); - + attribs.put("class", " abcd efgh raulpismuth"); hasClass = el.hasClass("raulpismuth"); assertTrue(hasClass); - + attribs.put("class", " abcd efgh raulpismuth "); hasClass = el.hasClass("raulpismuth"); assertTrue(hasClass); } - + @Test public void testClassUpdates() { Document doc = Jsoup.parse("<div class='mellow yellow'></div>"); Element div = doc.select("div").first(); @@ -327,7 +327,7 @@ public class ElementTest { Document doc = Jsoup.parse("<title>Format test</title><div><p>Hello <span>jsoup <span>users</span></span></p><p>Good.</p></div>"); assertEquals("<html>\n <head>\n <title>Format test</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body>\n <div>\n <p>Hello <span>jsoup <span>users</span></span></p>\n <p>Good.</p>\n </div>\n </body>\n</html>", doc.html()); } - + @Test public void testFormatOutline() { Document doc = Jsoup.parse("<title>Format test</title><div><p>Hello <span>jsoup <span>users</span></span></p><p>Good.</p></div>"); doc.outputSettings().outline(true); @@ -385,7 +385,7 @@ public class ElementTest { " </div>\n" + "</div>", doc.body().html()); } - + @Test public void testEmptyElementFormatHtml() { // don't put newlines into empty blocks Document doc = Jsoup.parse("<section><div></div></section>"); @@ -415,7 +415,7 @@ public class ElementTest { assertEquals("Gone", div.text()); assertEquals(0, doc.select("p").size()); } - + @Test public void testAddNewElement() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -431,24 +431,24 @@ public class ElementTest { assertEquals(i, ps.get(i).siblingIndex); } } - + @Test public void testAddBooleanAttribute() { Element div = new Element(Tag.valueOf("div"), ""); - + div.attr("true", true); - + div.attr("false", "value"); div.attr("false", false); - + assertTrue(div.hasAttr("true")); assertEquals("", div.attr("true")); - + List<Attribute> attributes = div.attributes().asList(); assertEquals("There should be one attribute", 1, attributes.size()); assertFalse(div.hasAttr("false")); - + assertEquals("<div true></div>", div.outerHtml()); - } + } @Test public void testAppendRowToTable() { Document doc = Jsoup.parse("<table><tr><td>1</td></tr></table>"); @@ -471,7 +471,7 @@ public class ElementTest { assertEquals(i, ps.get(i).siblingIndex); } } - + @Test public void testPrependElement() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -479,14 +479,14 @@ public class ElementTest { assertEquals("Before", div.child(0).text()); assertEquals("Hello", div.child(1).text()); } - + @Test public void testAddNewText() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.appendText(" there & now >"); assertEquals("<p>Hello</p> there &amp; now &gt;", TextUtil.stripNewlines(div.html())); } - + @Test public void testPrependText() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -506,7 +506,7 @@ public class ElementTest { Element div = doc.getElementById("1"); div.prependText(null); } - + @Test public void testAddNewHtml() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -519,7 +519,7 @@ public class ElementTest { assertEquals(i, ps.get(i).siblingIndex); } } - + @Test public void testPrependNewHtml() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -532,7 +532,7 @@ public class ElementTest { assertEquals(i, ps.get(i).siblingIndex); } } - + @Test public void testSetHtml() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -566,23 +566,23 @@ public class ElementTest { assertEquals(ret, p); } - + @Test public void before() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p1 = doc.select("p").first(); p1.before("<div>one</div><div>two</div>"); assertEquals("<div><div>one</div><div>two</div><p>Hello</p><p>There</p></div>", TextUtil.stripNewlines(doc.body().html())); - + doc.select("p").last().before("<p>Three</p><!-- four -->"); assertEquals("<div><div>one</div><div>two</div><p>Hello</p><p>Three</p><!-- four --><p>There</p></div>", TextUtil.stripNewlines(doc.body().html())); } - + @Test public void after() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p1 = doc.select("p").first(); p1.after("<div>one</div><div>two</div>"); assertEquals("<div><p>Hello</p><div>one</div><div>two</div><p>There</p></div>", TextUtil.stripNewlines(doc.body().html())); - + doc.select("p").last().after("<p>Three</p><!-- four -->"); assertEquals("<div><p>Hello</p><div>one</div><div>two</div><p>There</p><p>Three</p><!-- four --></div>", TextUtil.stripNewlines(doc.body().html())); } @@ -631,7 +631,7 @@ public class ElementTest { assertEquals("bacon", dataset.get("food")); attributes.put("data-", "empty"); - assertEquals(null, dataset.get("")); // data- is not a data attribute + assertNull(dataset.get("")); // data- is not a data attribute Element p = doc.select("p").first(); assertEquals(0, p.dataset().size()); @@ -906,9 +906,9 @@ public void testCssPath() { assertEquals(divB.cssSelector(), "html > body > div:nth-child(2)"); assertEquals(divC.cssSelector(), "html > body > div.c1.c2"); - assertTrue(divA == doc.select(divA.cssSelector()).first()); - assertTrue(divB == doc.select(divB.cssSelector()).first()); - assertTrue(divC == doc.select(divC.cssSelector()).first()); + assertSame(divA, doc.select(divA.cssSelector()).first()); + assertSame(divB, doc.select(divB.cssSelector()).first()); + assertSame(divC, doc.select(divC.cssSelector()).first()); } @@ -921,28 +921,28 @@ public void testClassNames() { final Set<String> set1 = div.classNames(); final Object[] arr1 = set1.toArray(); - assertTrue(arr1.length==2); + assertEquals(2, arr1.length); assertEquals("c1", arr1[0]); assertEquals("c2", arr1[1]); // Changes to the set should not be reflected in the Elements getters set1.add("c3"); - assertTrue(2==div.classNames().size()); + assertEquals(2, div.classNames().size()); assertEquals("c1 c2", div.className()); // Update the class names to a fresh set final Set<String> newSet = new LinkedHashSet<>(3); newSet.addAll(set1); newSet.add("c3"); - + div.classNames(newSet); - + assertEquals("c1 c2 c3", div.className()); final Set<String> set2 = div.classNames(); final Object[] arr2 = set2.toArray(); - assertTrue(arr2.length==3); + assertEquals(3, arr2.length); assertEquals("c1", arr2[0]); assertEquals("c2", arr2[1]); assertEquals("c3", arr2[2]); @@ -986,17 +986,17 @@ public void testHashAndEqualsAndValue() { assertTrue(e0.hasSameValue(e1)); assertTrue(e0.hasSameValue(e4)); assertTrue(e0.hasSameValue(e5)); - assertFalse(e0.equals(e2)); + assertNotEquals(e0, e2); assertFalse(e0.hasSameValue(e2)); assertFalse(e0.hasSameValue(e3)); assertFalse(e0.hasSameValue(e6)); assertFalse(e0.hasSameValue(e7)); assertEquals(e0.hashCode(), e0.hashCode()); - assertFalse(e0.hashCode() == (e2.hashCode())); - assertFalse(e0.hashCode() == (e3).hashCode()); - assertFalse(e0.hashCode() == (e6).hashCode()); - assertFalse(e0.hashCode() == (e7).hashCode()); + assertNotEquals(e0.hashCode(), (e2.hashCode())); + assertNotEquals(e0.hashCode(), (e3).hashCode()); + assertNotEquals(e0.hashCode(), (e6).hashCode()); + assertNotEquals(e0.hashCode(), (e7).hashCode()); } @Test public void testRelativeUrls() { @@ -1304,7 +1304,7 @@ public void testAppendTo() { assertTrue(matched.is(":containsOwn(Thisisonelongword)")); } - + @Test public void testRemoveBeforeIndex() { Document doc = Jsoup.parse( @@ -1321,7 +1321,7 @@ public void testRemoveBeforeIndex() { assertEquals("<body><div><p>XXX</p><p>after1</p><p>after2</p></div></body>", TextUtil.stripNewlines(body.outerHtml())); } - + @Test public void testRemoveAfterIndex() { Document doc2 = Jsoup.parse( @@ -1338,13 +1338,13 @@ public void testRemoveAfterIndex() { assertEquals("<body><div><p>before1</p><p>before2</p><p>XXX</p></div></body>", TextUtil.stripNewlines(body.outerHtml())); } - - @Test + + @Test public void whiteSpaceClassElement(){ Tag tag = Tag.valueOf("a"); Attributes attribs = new Attributes(); Element el = new Element(tag, "", attribs); - + attribs.put("class", "abc "); boolean hasClass = el.hasClass("ab"); assertFalse(hasClass); diff --git a/src/test/java/org/jsoup/nodes/EntitiesTest.java b/src/test/java/org/jsoup/nodes/EntitiesTest.java index 47f5e77610..05ed5fbce7 100644 --- a/src/test/java/org/jsoup/nodes/EntitiesTest.java +++ b/src/test/java/org/jsoup/nodes/EntitiesTest.java @@ -51,7 +51,6 @@ public class EntitiesTest { } @Test public void xhtml() { - String text = "&amp; &gt; &lt; &quot;"; assertEquals(38, xhtml.codepointForName("amp")); assertEquals(62, xhtml.codepointForName("gt")); assertEquals(60, xhtml.codepointForName("lt")); @@ -104,20 +103,20 @@ public class EntitiesTest { assertEquals("Hello &= &", Entities.unescape(text, false)); } - + @Test public void caseSensitive() { String unescaped = "Ü ü & &"; assertEquals("&Uuml; &uuml; &amp; &amp;", Entities.escape(unescaped, new OutputSettings().charset("ascii").escapeMode(extended))); - + String escaped = "&Uuml; &uuml; &amp; &AMP"; assertEquals("Ü ü & &", Entities.unescape(escaped)); } - + @Test public void quoteReplacements() { String escaped = "&#92; &#36;"; String unescaped = "\\ $"; - + assertEquals(unescaped, Entities.unescape(escaped)); } diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index a2221e8722..bcb060301f 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -173,6 +173,6 @@ public class FormElementTest { assertEquals(2, data.size()); assertEquals("user", data.get(0).key()); assertEquals("login", data.get(1).key()); - assertEquals(null, doc.selectFirst("input[name=pass]")); + assertNull(doc.selectFirst("input[name=pass]")); } } diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 722e4c46e8..11bc796c86 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -152,8 +152,8 @@ public void handlesAbsOnProtocolessAbsoluteUris() { @Test public void ownerDocument() { Document doc = Jsoup.parse("<p>Hello"); Element p = doc.select("p").first(); - assertTrue(p.ownerDocument() == doc); - assertTrue(doc.ownerDocument() == doc); + assertSame(p.ownerDocument(), doc); + assertSame(doc.ownerDocument(), doc); assertNull(doc.parent()); } @@ -161,15 +161,15 @@ public void handlesAbsOnProtocolessAbsoluteUris() { Document doc = Jsoup.parse("<div><p>Hello"); Element p = doc.select("p").first(); Node root = p.root(); - assertTrue(doc == root); + assertSame(doc, root); assertNull(root.parent()); - assertTrue(doc.root() == doc); - assertTrue(doc.root() == doc.ownerDocument()); + assertSame(doc.root(), doc); + assertSame(doc.root(), doc.ownerDocument()); Element standAlone = new Element(Tag.valueOf("p"), ""); - assertTrue(standAlone.parent() == null); - assertTrue(standAlone.root() == standAlone); - assertTrue(standAlone.ownerDocument() == null); + assertNull(standAlone.parent()); + assertSame(standAlone.root(), standAlone); + assertNull(standAlone.ownerDocument()); } @Test public void before() { @@ -214,7 +214,7 @@ public void handlesAbsOnProtocolessAbsoluteUris() { Element span = doc.select("span").first(); Node node = span.unwrap(); assertEquals("<div>One Two</div>", TextUtil.stripNewlines(doc.body().html())); - assertTrue(node == null); + assertNull(node); } @Test public void traverse() { @@ -282,14 +282,14 @@ public void tail(Node node, int depth) { Element elClone = doc.clone().select("div").first(); assertTrue(elClone.hasClass("foo")); - assertTrue(elClone.text().equals("Text")); + assertEquals("Text", elClone.text()); el.removeClass("foo"); el.text("None"); assertFalse(el.hasClass("foo")); assertTrue(elClone.hasClass("foo")); - assertTrue(el.text().equals("None")); - assertTrue(elClone.text().equals("Text")); + assertEquals("None", el.text()); + assertEquals("Text", elClone.text()); } @Test public void changingAttributeValueShouldReplaceExistingAttributeCaseInsensitive() { diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index df08b5674a..535bfbcb56 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -27,7 +27,7 @@ public class TextNodeTest { assertFalse(four.isBlank()); assertFalse(five.isBlank()); } - + @Test public void testTextBean() { Document doc = Jsoup.parse("<p>One <span>two &amp;</span> three &amp;</p>"); Element p = doc.select("p").first(); @@ -36,10 +36,10 @@ public class TextNodeTest { assertEquals("two &", span.text()); TextNode spanText = (TextNode) span.childNode(0); assertEquals("two &", spanText.text()); - + TextNode tn = (TextNode) p.childNode(2); assertEquals(" three &", tn.text()); - + tn.text(" POW!"); assertEquals("One <span>two &amp;</span> POW!", TextUtil.stripNewlines(p.html())); @@ -57,7 +57,7 @@ public class TextNodeTest { assertEquals("there", tail.getWholeText()); tail.text("there!"); assertEquals("Hello there!", div.text()); - assertTrue(tn.parent() == tail.parent()); + assertSame(tn.parent(), tail.parent()); } @Test public void testSplitAnEmbolden() { diff --git a/src/test/java/org/jsoup/parser/AttributeParseTest.java b/src/test/java/org/jsoup/parser/AttributeParseTest.java index 60bfab0a0d..4967c7bebe 100644 --- a/src/test/java/org/jsoup/parser/AttributeParseTest.java +++ b/src/test/java/org/jsoup/parser/AttributeParseTest.java @@ -90,7 +90,7 @@ public class AttributeParseTest { @Test public void dropsSlashFromAttributeName() { String html = "<img /onerror='doMyJob'/>"; Document doc = Jsoup.parse(html); - assertTrue("SelfClosingStartTag ignores last character", !doc.select("img[onerror]").isEmpty()); + assertFalse("SelfClosingStartTag ignores last character", doc.select("img[onerror]").isEmpty()); assertEquals("<img onerror=\"doMyJob\">", doc.body().html()); doc = Jsoup.parse(html, "", Parser.xmlParser()); diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 19a9530457..633aa4f96b 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -244,10 +244,10 @@ public void matchesIgnoreCase() { assertEquals("Check", two); assertEquals("Check", three); assertEquals("CHOKE", four); - assertTrue(one == two); - assertTrue(two == three); - assertTrue(three != four); - assertTrue(four != five); + assertSame(one, two); + assertSame(two, three); + assertNotSame(three, four); + assertNotSame(four, five); assertEquals(five, "A string that is longer than 16 chars"); } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index c9a3df7432..6e8302549f 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1191,7 +1191,7 @@ public void testInvalidTableContents() throws IOException { assertEquals(1, doc.select("p").size()); } - @Test public void commentAtEnd() throws Exception { + @Test public void commentAtEnd() { Document doc = Jsoup.parse("<!"); assertTrue(doc.childNode(0) instanceof Comment); } diff --git a/src/test/java/org/jsoup/parser/ParserSettingsTest.java b/src/test/java/org/jsoup/parser/ParserSettingsTest.java index b1bcd13bbd..a4903d6b82 100644 --- a/src/test/java/org/jsoup/parser/ParserSettingsTest.java +++ b/src/test/java/org/jsoup/parser/ParserSettingsTest.java @@ -30,7 +30,7 @@ public class ParserSettingsTest { assertEquals("ID", attrOn.normalizeAttribute("ID")); } - @Test @MultiLocaleTest public void attributeCaseNormalization() throws Exception { + @Test @MultiLocaleTest public void attributeCaseNormalization() { ParseSettings parseSettings = new ParseSettings(false, false); String normalizedAttribute = parseSettings.normalizeAttribute("HIDDEN"); @@ -38,7 +38,7 @@ public class ParserSettingsTest { assertEquals("hidden", normalizedAttribute); } - @Test @MultiLocaleTest public void attributesCaseNormalization() throws Exception { + @Test @MultiLocaleTest public void attributesCaseNormalization() { ParseSettings parseSettings = new ParseSettings(false, false); Attributes attributes = new Attributes(); attributes.put("ITEM", "1"); diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java index 1ed9a41ec4..9f039a6a6e 100644 --- a/src/test/java/org/jsoup/parser/TokenQueueTest.java +++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java @@ -84,14 +84,14 @@ private static void validateNestedQuotes(String html, String selector) { } @Test - public void chompBalancedThrowIllegalArgumentException() throws Exception { + public void chompBalancedThrowIllegalArgumentException() { try { TokenQueue tq = new TokenQueue("unbalanced(something(or another)) else"); tq.consumeTo("("); tq.chompBalanced('(', '+'); org.junit.Assert.fail("should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { - assertEquals("Did not find balanced marker at \'something(or another)) else\'", expected.getMessage()); + assertEquals("Did not find balanced marker at 'something(or another)) else'", expected.getMessage()); } } } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 79efc34bb4..5dd18d85b6 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -80,7 +80,7 @@ public void testSupplyParserToConnection() throws IOException { assertEquals("<doc><val>One<val>Two</val>Three</val></doc>", TextUtil.stripNewlines(xmlDoc.html())); - assertFalse(htmlDoc.equals(xmlDoc)); + assertNotEquals(htmlDoc, xmlDoc); assertEquals(xmlDoc, autoXmlDoc); assertEquals(1, htmlDoc.select("head").size()); // html parser normalises assertEquals(0, xmlDoc.select("head").size()); // xml parser does not diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index a09c98d704..e7244df99a 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -293,8 +293,8 @@ public void tail(Node node, int depth) { List<FormElement> forms = els.forms(); assertEquals(2, forms.size()); - assertTrue(forms.get(0) != null); - assertTrue(forms.get(1) != null); + assertNotNull(forms.get(0)); + assertNotNull(forms.get(1)); assertEquals("1", forms.get(0).id()); assertEquals("2", forms.get(1).id()); } From 8c50f6f6a76964a0f16e4e3aea127321903c079a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 5 Mar 2020 09:02:07 -0800 Subject: [PATCH 432/774] Minor parse speed improvement --- .../org/jsoup/parser/CharacterReader.java | 26 +++++++++++++++++ src/main/java/org/jsoup/parser/Tokeniser.java | 29 ++++++++++++++++--- .../java/org/jsoup/parser/TokeniserState.java | 15 +++++----- .../org/jsoup/parser/TokeniserStateTest.java | 2 -- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index ef1590bc48..d4f0d2b859 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -311,6 +311,32 @@ String consumeData() { return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } + String consumeAttributeQuoted(final boolean single) { + // null, " or ', & + //bufferUp(); // no need to bufferUp, just called consume() + int pos = bufPos; + final int start = pos; + final int remaining = bufLength; + final char[] val = charBuf; + + OUTER: while (pos < remaining) { + switch (val[pos]) { + case '&': + case TokeniserState.nullChar: + break OUTER; + case '\'': + if (single) break OUTER; + case '"': + if (!single) break OUTER;; + default: + pos++; + } + } + bufPos = pos; + return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; + } + + String consumeRawData() { // <, null //bufferUp(); // no need to bufferUp, just called consume() diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 788d8bd817..6d2e844561 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -104,6 +104,31 @@ void emit(final String str) { } } + // variations to limit need to create temp strings + void emit(final StringBuilder str) { + if (charsString == null) { + charsString = str.toString(); + } + else { + if (charsBuilder.length() == 0) { + charsBuilder.append(charsString); + } + charsBuilder.append(str); + } + } + + void emit(char c) { + if (charsString == null) { + charsString = String.valueOf(c); + } + else { + if (charsBuilder.length() == 0) { + charsBuilder.append(charsString); + } + charsBuilder.append(c); + } + } + void emit(char[] chars) { emit(String.valueOf(chars)); } @@ -112,10 +137,6 @@ void emit(int[] codepoints) { emit(new String(codepoints, 0, codepoints.length)); } - void emit(char c) { - emit(String.valueOf(c)); - } - TokeniserState getState() { return state; } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index d81cecce06..74e96fa56f 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -253,7 +253,8 @@ void read(Tokeniser t, CharacterReader r) { } private void anythingElse(Tokeniser t, CharacterReader r) { - t.emit("</" + t.dataBuffer.toString()); + t.emit("</"); + t.emit(t.dataBuffer); r.unconsume(); t.transition(Rcdata); } @@ -423,7 +424,8 @@ void read(Tokeniser t, CharacterReader r) { if (r.matchesLetter()) { t.createTempBuffer(); t.dataBuffer.append(r.current()); - t.emit("<" + r.current()); + t.emit("<"); + t.emit(r.current()); t.advanceTransition(ScriptDataDoubleEscapeStart); } else if (r.matches('/')) { t.createTempBuffer(); @@ -744,7 +746,7 @@ void read(Tokeniser t, CharacterReader r) { }, AttributeValue_doubleQuoted { void read(Tokeniser t, CharacterReader r) { - String value = r.consumeToAnySorted(attributeDoubleValueCharsSorted); + String value = r.consumeAttributeQuoted(false); if (value.length() > 0) t.tagPending.appendAttributeValue(value); else @@ -777,7 +779,7 @@ void read(Tokeniser t, CharacterReader r) { }, AttributeValue_singleQuoted { void read(Tokeniser t, CharacterReader r) { - String value = r.consumeToAnySorted(attributeSingleValueCharsSorted); + String value = r.consumeAttributeQuoted(true); if (value.length() > 0) t.tagPending.appendAttributeValue(value); else @@ -1630,8 +1632,6 @@ void read(Tokeniser t, CharacterReader r) { static final char nullChar = '\u0000'; // char searches. must be sorted, used in inSorted. MUST update TokenisetStateTest if more arrays are added. - static final char[] attributeSingleValueCharsSorted = new char[]{nullChar, '&', '\''}; - static final char[] attributeDoubleValueCharsSorted = new char[]{nullChar, '"', '&'}; static final char[] attributeNameCharsSorted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '\'', '/', '<', '=', '>'}; static final char[] attributeValueUnquoted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '&', '\'', '<', '=', '>', '`'}; @@ -1678,7 +1678,8 @@ private static void handleDataEndTag(Tokeniser t, CharacterReader r, TokeniserSt } if (needsExitTransition) { - t.emit("</" + t.dataBuffer.toString()); + t.emit("</"); + t.emit(t.dataBuffer); t.transition(elseTransition); } } diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index f19391091e..bc4280b64a 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -22,8 +22,6 @@ public class TokeniserStateTest { @Test public void ensureSearchArraysAreSorted() { char[][] arrays = { - TokeniserState.attributeSingleValueCharsSorted, - TokeniserState.attributeDoubleValueCharsSorted, TokeniserState.attributeNameCharsSorted, TokeniserState.attributeValueUnquoted }; From 8e432a5f5fde4694834ce23c5ac1503ce8d381bc Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 5 Mar 2020 10:09:53 -0800 Subject: [PATCH 433/774] Java8 and JUnit5 (#1335) * Migration to java8. * Tests ported to JUnit 5. * Surefire plugin added. * Assertions and Rules ported to JUnit 5. * JUnit 4 dependency removed. * MultiLocale annotation renamed and changed to include the @ParameterizedTest; multi locale tests need only a single annotation now. --- .travis.yml | 1 - pom.xml | 33 +++++++--- .../java/org/jsoup/MultiLocaleExtension.java | 38 +++++++++++ src/test/java/org/jsoup/MultiLocaleRule.java | 42 ------------ .../java/org/jsoup/helper/DataUtilTest.java | 63 +++++++++--------- .../org/jsoup/helper/HttpConnectionTest.java | 65 +++++++++---------- .../java/org/jsoup/helper/W3CDomTest.java | 20 ++---- .../java/org/jsoup/integration/ConnectIT.java | 50 +++++++------- .../org/jsoup/integration/ConnectTest.java | 49 +++++++------- .../java/org/jsoup/integration/ParseTest.java | 4 +- .../org/jsoup/integration/UrlConnectTest.java | 18 ++--- .../integration/servlets/Deflateservlet.java | 1 - .../integration/servlets/FileServlet.java | 1 - .../integration/servlets/HelloServlet.java | 1 - .../integration/servlets/RedirectServlet.java | 4 +- .../ConstrainableInputStreamTest.java | 19 +++--- .../org/jsoup/internal/StringUtilTest.java | 13 ++-- .../java/org/jsoup/nodes/AttributeTest.java | 19 +++--- .../java/org/jsoup/nodes/AttributesTest.java | 10 ++- .../java/org/jsoup/nodes/BuildEntities.java | 9 ++- .../java/org/jsoup/nodes/CommentTest.java | 4 +- .../java/org/jsoup/nodes/DocumentTest.java | 33 ++++------ .../org/jsoup/nodes/DocumentTypeTest.java | 13 ++-- src/test/java/org/jsoup/nodes/ElementIT.java | 6 +- .../java/org/jsoup/nodes/ElementTest.java | 42 +++++------- .../java/org/jsoup/nodes/EntitiesTest.java | 4 +- .../java/org/jsoup/nodes/FormElementTest.java | 6 +- .../java/org/jsoup/nodes/LeafNodeTest.java | 4 +- src/test/java/org/jsoup/nodes/NodeTest.java | 15 +++-- .../java/org/jsoup/nodes/TextNodeTest.java | 4 +- .../org/jsoup/parser/AttributeParseTest.java | 12 ++-- .../org/jsoup/parser/CharacterReaderTest.java | 6 +- .../java/org/jsoup/parser/HtmlParserTest.java | 30 ++++----- .../parser/HtmlTreeBuilderStateTest.java | 13 ++-- .../org/jsoup/parser/HtmlTreeBuilderTest.java | 5 +- src/test/java/org/jsoup/parser/ParserIT.java | 6 +- .../org/jsoup/parser/ParserSettingsTest.java | 25 ++++--- .../java/org/jsoup/parser/ParserTest.java | 4 +- src/test/java/org/jsoup/parser/TagTest.java | 25 +++---- .../java/org/jsoup/parser/TokenQueueTest.java | 7 +- .../org/jsoup/parser/TokeniserStateTest.java | 10 +-- .../java/org/jsoup/parser/TokeniserTest.java | 28 +++----- .../org/jsoup/parser/XmlTreeBuilderTest.java | 20 ++---- .../java/org/jsoup/safety/CleanerTest.java | 53 ++++++++------- src/test/java/org/jsoup/select/CssTest.java | 46 ++++++------- .../java/org/jsoup/select/ElementsTest.java | 12 +--- .../org/jsoup/select/QueryParserTest.java | 23 ++++--- .../java/org/jsoup/select/SelectorTest.java | 52 +++++++++------ .../java/org/jsoup/select/TraversorTest.java | 6 +- 49 files changed, 477 insertions(+), 497 deletions(-) create mode 100644 src/test/java/org/jsoup/MultiLocaleExtension.java delete mode 100644 src/test/java/org/jsoup/MultiLocaleRule.java diff --git a/.travis.yml b/.travis.yml index 23100bba70..988bd1b80d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ env: # List any JDK you want to build your software with. # You can see the list of supported environments by installing Jabba and using ls-remote: # https://github.com/shyiko/jabba#usage - - JDK="openjdk-ri@1.7." - JDK="adopt@1.8." - JDK="amazon-corretto@1.8." - JDK="openjdk@1.9." diff --git a/pom.xml b/pom.xml index 19034a2f4b..b691ec7f03 100644 --- a/pom.xml +++ b/pom.xml @@ -38,8 +38,8 @@ <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> - <source>1.7</source> - <target>1.7</target> + <source>1.8</source> + <target>1.8</target> <encoding>UTF-8</encoding> <compilerArgs> <!-- saves output for package-info.java, so mvn sees it has completed it, so incremental compile works --> @@ -50,7 +50,7 @@ </configuration> </plugin> <plugin> - <!-- this plugin allows us to ensure Java 7 API compatibility --> + <!-- this plugin allows us to ensure Java 8 API compatibility --> <groupId>org.codehaus.mojo</groupId> <artifactId>animal-sniffer-maven-plugin</artifactId> <version>1.16</version> @@ -64,7 +64,7 @@ <configuration> <signature> <groupId>org.codehaus.mojo.signature</groupId> - <artifactId>java17</artifactId> + <artifactId>java18</artifactId> <version>1.0</version> </signature> <signature> @@ -157,6 +157,11 @@ <artifactId>maven-release-plugin</artifactId> <version>2.5.3</version> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M3</version> @@ -251,11 +256,23 @@ <dependencies> + <!-- junit --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.6.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.6.0</version> + <scope>test</scope> + </dependency> <dependency> - <!-- junit --> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.12</version> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>5.6.0</version> <scope>test</scope> </dependency> diff --git a/src/test/java/org/jsoup/MultiLocaleExtension.java b/src/test/java/org/jsoup/MultiLocaleExtension.java new file mode 100644 index 0000000000..7ce23619e9 --- /dev/null +++ b/src/test/java/org/jsoup/MultiLocaleExtension.java @@ -0,0 +1,38 @@ +package org.jsoup; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.lang.annotation.*; +import java.util.Locale; +import java.util.stream.Stream; + +public class MultiLocaleExtension implements AfterEachCallback, ArgumentsProvider { + private final Locale defaultLocale = Locale.getDefault(); + + @Override + public void afterEach(ExtensionContext context) { + Locale.setDefault(defaultLocale); + } + + @Override + public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) { + return Stream.of(Arguments.of(Locale.ENGLISH), Arguments.arguments(new Locale("tr"))); + } + + + @Documented + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ArgumentsSource(MultiLocaleExtension.class) + @ExtendWith(MultiLocaleExtension.class) + @ParameterizedTest + public @interface MultiLocaleTest { + } + +} diff --git a/src/test/java/org/jsoup/MultiLocaleRule.java b/src/test/java/org/jsoup/MultiLocaleRule.java deleted file mode 100644 index 4e63f09cc0..0000000000 --- a/src/test/java/org/jsoup/MultiLocaleRule.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.jsoup; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Locale; - -public class MultiLocaleRule implements TestRule { - @Retention(RetentionPolicy.RUNTIME) - public @interface MultiLocaleTest { - } - - @Override - public Statement apply(final Statement statement, final Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - MultiLocaleTest annotation = description.getAnnotation(MultiLocaleTest.class); - if (annotation == null) { - statement.evaluate(); - return; - } - - evaluateWithLocale(Locale.ENGLISH); - evaluateWithLocale(new Locale("tr")); - } - - private void evaluateWithLocale(Locale locale) throws Throwable { - Locale oldLocale = Locale.getDefault(); - Locale.setDefault(locale); - try { - statement.evaluate(); - } finally { - Locale.setDefault(oldLocale); - } - } - }; - } -} diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 32ecd5fa62..f585bb26c4 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -3,20 +3,13 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.nio.charset.StandardCharsets; import static org.jsoup.integration.ParseTest.getFile; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.*; public class DataUtilTest { @Test @@ -24,16 +17,17 @@ public void testCharset() { assertEquals("utf-8", DataUtil.getCharsetFromContentType("text/html;charset=utf-8 ")); assertEquals("UTF-8", DataUtil.getCharsetFromContentType("text/html; charset=UTF-8")); assertEquals("ISO-8859-1", DataUtil.getCharsetFromContentType("text/html; charset=ISO-8859-1")); - assertEquals(null, DataUtil.getCharsetFromContentType("text/html")); - assertEquals(null, DataUtil.getCharsetFromContentType(null)); - assertEquals(null, DataUtil.getCharsetFromContentType("text/html;charset=Unknown")); + assertNull(DataUtil.getCharsetFromContentType("text/html")); + assertNull(DataUtil.getCharsetFromContentType(null)); + assertNull(DataUtil.getCharsetFromContentType("text/html;charset=Unknown")); } - @Test public void testQuotedCharset() { + @Test + public void testQuotedCharset() { assertEquals("utf-8", DataUtil.getCharsetFromContentType("text/html; charset=\"utf-8\"")); assertEquals("UTF-8", DataUtil.getCharsetFromContentType("text/html;charset=\"UTF-8\"")); assertEquals("ISO-8859-1", DataUtil.getCharsetFromContentType("text/html; charset=\"ISO-8859-1\"")); - assertEquals(null, DataUtil.getCharsetFromContentType("text/html; charset=\"Unsupported\"")); + assertNull(DataUtil.getCharsetFromContentType("text/html; charset=\"Unsupported\"")); assertEquals("UTF-8", DataUtil.getCharsetFromContentType("text/html; charset='UTF-8'")); } @@ -50,13 +44,15 @@ private InputStream stream(String data, String charset) { return null; } - @Test public void discardsSpuriousByteOrderMark() throws IOException { + @Test + public void discardsSpuriousByteOrderMark() throws IOException { String html = "\uFEFF<html><head><title>One</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>Two</body></html>"; Document doc = DataUtil.parseInputStream(stream(html), "UTF-8", "http://foo.com/", Parser.htmlParser()); assertEquals("One", doc.head().text()); } - @Test public void discardsSpuriousByteOrderMarkWhenNoCharsetSet() throws IOException { + @Test + public void discardsSpuriousByteOrderMarkWhenNoCharsetSet() throws IOException { String html = "\uFEFF<html><head><title>One</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>Two</body></html>"; Document doc = DataUtil.parseInputStream(stream(html), null, "http://foo.com/", Parser.htmlParser()); assertEquals("One", doc.head().text()); @@ -65,8 +61,8 @@ private InputStream stream(String data, String charset) { @Test public void shouldNotThrowExceptionOnEmptyCharset() { - assertEquals(null, DataUtil.getCharsetFromContentType("text/html; charset=")); - assertEquals(null, DataUtil.getCharsetFromContentType("text/html; charset=;")); + assertNull(DataUtil.getCharsetFromContentType("text/html; charset=")); + assertNull(DataUtil.getCharsetFromContentType("text/html; charset=;")); } @Test @@ -81,7 +77,7 @@ public void shouldCorrectCharsetForDuplicateCharsetString() { @Test public void shouldReturnNullForIllegalCharsetNames() { - assertEquals(null, DataUtil.getCharsetFromContentType("text/html; charset=$HJKDF§$/(")); + assertNull(DataUtil.getCharsetFromContentType("text/html; charset=$HJKDF§$/(")); } @Test @@ -93,7 +89,7 @@ public void generatesMimeBoundaries() { assertEquals(DataUtil.boundaryLength, m2.length()); assertNotSame(m1, m2); } - + @Test public void wrongMetaCharsetFallback() throws IOException { String html = "<html><head><meta charset=iso-8> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>"; @@ -101,11 +97,11 @@ public void wrongMetaCharsetFallback() throws IOException { Document doc = DataUtil.parseInputStream(stream(html), null, "http://example.com", Parser.htmlParser()); final String expected = "<html>\n" + - " <head>\n" + - " <meta charset=\"iso-8\">\n" + - " <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body></body>\n" + - "</html>"; + " <head>\n" + + " <meta charset=\"iso-8\">\n" + + " <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + + " <body></body>\n" + + "</html>"; assertEquals(expected, doc.toString()); } @@ -177,9 +173,9 @@ public void supportsZippedUTF8BOM() throws IOException { public void supportsXmlCharsetDeclaration() throws IOException { String encoding = "iso-8859-1"; InputStream soup = new ByteArrayInputStream(( - "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" + - "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">Hellö Wörld!</html>" + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" + + "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">Hellö Wörld!</html>" ).getBytes(encoding)); Document doc = Jsoup.parse(soup, null, ""); @@ -187,14 +183,16 @@ public void supportsXmlCharsetDeclaration() throws IOException { } - @Test public void lLoadsGzipFile() throws IOException { + @Test + public void lLoadsGzipFile() throws IOException { File in = getFile("/htmltests/gzip.html.gz"); Document doc = Jsoup.parse(in, null); assertEquals("Gzip test", doc.title()); assertEquals("This is a gzipped HTML file.", doc.selectFirst("p").text()); } - @Test public void loadsZGzipFile() throws IOException { + @Test + public void loadsZGzipFile() throws IOException { // compressed on win, with z suffix File in = getFile("/htmltests/gzip.html.z"); Document doc = Jsoup.parse(in, null); @@ -202,7 +200,8 @@ public void supportsXmlCharsetDeclaration() throws IOException { assertEquals("This is a gzipped HTML file.", doc.selectFirst("p").text()); } - @Test public void handlesFakeGzipFile () throws IOException { + @Test + public void handlesFakeGzipFile() throws IOException { File in = getFile("/htmltests/fake-gzip.html.gz"); Document doc = Jsoup.parse(in, null); assertEquals("This is not gzipped", doc.title()); diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 896383d8b6..2b85b92193 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -1,48 +1,45 @@ package org.jsoup.helper; import org.jsoup.Connection; -import org.jsoup.Jsoup; -import org.jsoup.MultiLocaleRule; -import org.jsoup.MultiLocaleRule.MultiLocaleTest; +import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.integration.ParseTest; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class HttpConnectionTest { /* most actual network http connection tests are in integration */ - @Rule public MultiLocaleRule rule = new MultiLocaleRule(); - - @Test(expected=IllegalArgumentException.class) public void throwsExceptionOnParseWithoutExecute() throws IOException { - Connection con = HttpConnection.connect("http://example.com"); - con.response().parse(); + @Test public void throwsExceptionOnParseWithoutExecute() { + assertThrows(IllegalArgumentException.class, () -> { + Connection con = HttpConnection.connect("http://example.com"); + con.response().parse(); + }); } - @Test(expected=IllegalArgumentException.class) public void throwsExceptionOnBodyWithoutExecute() throws IOException { - Connection con = HttpConnection.connect("http://example.com"); - con.response().body(); + @Test public void throwsExceptionOnBodyWithoutExecute() { + assertThrows(IllegalArgumentException.class, () -> { + Connection con = HttpConnection.connect("http://example.com"); + con.response().body(); + }); } - @Test(expected=IllegalArgumentException.class) public void throwsExceptionOnBodyAsBytesWithoutExecute() throws IOException { - Connection con = HttpConnection.connect("http://example.com"); - con.response().bodyAsBytes(); + @Test public void throwsExceptionOnBodyAsBytesWithoutExecute() { + assertThrows(IllegalArgumentException.class, () -> { + Connection con = HttpConnection.connect("http://example.com"); + con.response().bodyAsBytes(); + }); } - @Test @MultiLocaleTest public void caseInsensitiveHeaders() { + @MultiLocaleTest + public void caseInsensitiveHeaders(Locale locale) { + Locale.setDefault(locale); + Connection.Response res = new HttpConnection.Response(); res.header("Accept-Encoding", "gzip"); res.header("content-type", "text/html"); @@ -122,13 +119,13 @@ public class HttpConnectionTest { headers = req.multiHeaders(); assertEquals("Bar", headers.get("Foo").get(0)); assertFalse(req.hasHeader("Accept")); - assertTrue(headers.get("Accept") == null); + assertNull(headers.get("Accept")); } @Test public void ignoresEmptySetCookies() { // prep http response header map Map<String, List<String>> headers = new HashMap<>(); - headers.put("Set-Cookie", Collections.<String>emptyList()); + headers.put("Set-Cookie", Collections.emptyList()); HttpConnection.Response res = new HttpConnection.Response(); res.processResponseHeaders(headers); assertEquals(0, res.cookies().size()); @@ -160,8 +157,8 @@ public class HttpConnectionTest { assertEquals("http://example.com", con.request().url().toExternalForm()); } - @Test(expected=IllegalArgumentException.class) public void throwsOnMalformedUrl() { - Connection con = HttpConnection.connect("bzzt"); + @Test public void throwsOnMalformedUrl() { + assertThrows(IllegalArgumentException.class, () -> HttpConnection.connect("bzzt")); } @Test public void userAgent() { @@ -191,9 +188,11 @@ public class HttpConnectionTest { assertEquals(Connection.Method.POST, con.request().method()); } - @Test(expected=IllegalArgumentException.class) public void throwsOnOddData() { - Connection con = HttpConnection.connect("http://example.com/"); - con.data("Name", "val", "what"); + @Test public void throwsOnOddData() { + assertThrows(IllegalArgumentException.class, () -> { + Connection con = HttpConnection.connect("http://example.com/"); + con.data("Name", "val", "what"); + }); } @Test public void data() { diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 6b84e52457..02bf00fe81 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -4,14 +4,12 @@ import org.jsoup.TextUtil; import org.jsoup.integration.ParseTest; import org.jsoup.nodes.Element; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; -import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -27,7 +25,7 @@ import java.nio.charset.StandardCharsets; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class W3CDomTest { @@ -36,15 +34,11 @@ private static Document parseXml(String xml, boolean nameSpaceAware) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(nameSpaceAware); DocumentBuilder builder = factory.newDocumentBuilder(); - builder.setEntityResolver(new EntityResolver() { - @Override - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException { - if (systemId.contains("about:legacy-compat")) { // <!doctype html> - return new InputSource(new StringReader("")); - } else { - return null; - } + builder.setEntityResolver((publicId, systemId) -> { + if (systemId.contains("about:legacy-compat")) { // <!doctype html> + return new InputSource(new StringReader("")); + } else { + return null; } }); Document dom = builder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java index 19bfb8f760..4e74c35b70 100644 --- a/src/test/java/org/jsoup/integration/ConnectIT.java +++ b/src/test/java/org/jsoup/integration/ConnectIT.java @@ -5,13 +5,13 @@ import org.jsoup.integration.servlets.SlowRider; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.SocketTimeoutException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Failsafe integration tests for Connect methods. These take a bit longer to run, so included as Integ, not Unit, tests. @@ -22,18 +22,16 @@ public class ConnectIT { public void canInterruptBodyStringRead() throws InterruptedException { // todo - implement in interruptable channels, so it's immediate final String[] body = new String[1]; - Thread runner = new Thread(new Runnable() { - public void run() { - try { - Connection.Response res = Jsoup.connect(SlowRider.Url) - .timeout(15 * 1000) - .execute(); - body[0] = res.body(); - } catch (IOException e) { - throw new RuntimeException(e); - } - + Thread runner = new Thread(() -> { + try { + Connection.Response res = Jsoup.connect(SlowRider.Url) + .timeout(15 * 1000) + .execute(); + body[0] = res.body(); + } catch (IOException e) { + throw new RuntimeException(e); } + }); runner.start(); @@ -50,18 +48,16 @@ public void run() { public void canInterruptDocumentRead() throws InterruptedException { // todo - implement in interruptable channels, so it's immediate final String[] body = new String[1]; - Thread runner = new Thread(new Runnable() { - public void run() { - try { - Connection.Response res = Jsoup.connect(SlowRider.Url) - .timeout(15 * 1000) - .execute(); - body[0] = res.parse().text(); - } catch (IOException e) { - throw new RuntimeException(e); - } - + Thread runner = new Thread(() -> { + try { + Connection.Response res = Jsoup.connect(SlowRider.Url) + .timeout(15 * 1000) + .execute(); + body[0] = res.parse().text(); + } catch (IOException e) { + throw new RuntimeException(e); } + }); runner.start(); @@ -83,8 +79,8 @@ public void totalTimeout() throws IOException { } catch (SocketTimeoutException e) { long end = System.currentTimeMillis(); long took = end - start; - assertTrue(("Time taken was " + took), took > timeout); - assertTrue(("Time taken was " + took), took < timeout * 1.8); + assertTrue(took > timeout, ("Time taken was " + took)); + assertTrue(took < timeout * 1.8, ("Time taken was " + took)); threw = true; } diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 7b612ddd71..663ad448ac 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -3,17 +3,12 @@ import org.jsoup.Connection; import org.jsoup.HttpStatusException; import org.jsoup.Jsoup; -import org.jsoup.integration.servlets.Deflateservlet; -import org.jsoup.integration.servlets.EchoServlet; -import org.jsoup.integration.servlets.FileServlet; -import org.jsoup.integration.servlets.HelloServlet; -import org.jsoup.integration.servlets.InterruptedServlet; -import org.jsoup.integration.servlets.RedirectServlet; +import org.jsoup.integration.servlets.*; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileInputStream; @@ -25,7 +20,7 @@ import static org.jsoup.helper.HttpConnection.CONTENT_TYPE; import static org.jsoup.helper.HttpConnection.MULTIPART_FORM_DATA; import static org.jsoup.integration.UrlConnectTest.browserUa; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests Jsoup.connect against a local server. @@ -33,13 +28,13 @@ public class ConnectTest { private static String echoUrl; - @BeforeClass + @BeforeAll public static void setUp() { TestServer.start(); echoUrl = EchoServlet.Url; } - @AfterClass + @AfterAll public static void tearDown() { TestServer.stop(); } @@ -270,7 +265,8 @@ public void postFiles() throws IOException { */ } - @Test public void multipleParsesOkAfterBufferUp() throws IOException { + @Test + public void multipleParsesOkAfterBufferUp() throws IOException { Connection.Response res = Jsoup.connect(echoUrl).execute().bufferUp(); Document doc = res.parse(); @@ -280,13 +276,17 @@ public void postFiles() throws IOException { assertTrue(doc2.title().contains("Environment")); } - @Test(expected=IllegalArgumentException.class) public void bodyAfterParseThrowsValidationError() throws IOException { - Connection.Response res = Jsoup.connect(echoUrl).execute(); - Document doc = res.parse(); - String body = res.body(); + @Test + public void bodyAfterParseThrowsValidationError() { + assertThrows(IllegalArgumentException.class, () -> { + Connection.Response res = Jsoup.connect(echoUrl).execute(); + Document doc = res.parse(); + String body = res.body(); + }); } - @Test public void bodyAndBytesAvailableBeforeParse() throws IOException { + @Test + public void bodyAndBytesAvailableBeforeParse() throws IOException { Connection.Response res = Jsoup.connect(echoUrl).execute(); String body = res.body(); assertTrue(body.contains("Environment")); @@ -297,11 +297,14 @@ public void postFiles() throws IOException { assertTrue(doc.title().contains("Environment")); } - @Test(expected=IllegalArgumentException.class) public void parseParseThrowsValidates() throws IOException { - Connection.Response res = Jsoup.connect(echoUrl).execute(); - Document doc = res.parse(); - assertTrue(doc.title().contains("Environment")); - Document doc2 = res.parse(); // should blow up because the response input stream has been drained + @Test + public void parseParseThrowsValidates() { + assertThrows(IllegalArgumentException.class, () -> { + Connection.Response res = Jsoup.connect(echoUrl).execute(); + Document doc = res.parse(); + assertTrue(doc.title().contains("Environment")); + Document doc2 = res.parse(); // should blow up because the response input stream has been drained + }); } diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index 7e8c8f1de6..f3c1dfcb4c 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -7,7 +7,7 @@ import org.jsoup.parser.ParseErrorList; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.*; import java.net.URISyntaxException; @@ -17,7 +17,7 @@ import java.nio.file.Files; import java.util.zip.GZIPInputStream; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Integration test: parses from real-world example HTML. diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index e27388a70a..b9a6deace8 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -3,15 +3,15 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.UnsupportedMimeTypeException; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.W3CDom; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Document; import org.jsoup.nodes.FormElement; import org.jsoup.parser.HtmlTreeBuilder; import org.jsoup.parser.Parser; import org.jsoup.parser.XmlTreeBuilder; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileInputStream; @@ -22,13 +22,13 @@ import java.net.URL; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** Tests the URL connection. Not enabled by default, so tests don't require network connection. @author Jonathan Hedley, jonathan@hedley.net */ -@Ignore // ignored by default so tests don't require network access. comment out to enable. +@Disabled // ignored by default so tests don't require network access. comment out to enable. // todo: rebuild these into a local Jetty test server, so not reliant on the vagaries of the internet. public class UrlConnectTest { private static final String WEBSITE_WITH_INVALID_CERTIFICATE = "https://certs.cac.washington.edu/CAtest/"; @@ -256,10 +256,10 @@ public void maxBodySize() throws IOException { * * @throws Exception */ - @Test(expected = IOException.class) + @Test public void testUnsafeFail() throws Exception { String url = WEBSITE_WITH_INVALID_CERTIFICATE; - Jsoup.connect(url).execute(); + assertThrows(IOException.class, () -> Jsoup.connect(url).execute()); } @@ -272,9 +272,9 @@ public void testUnsafeFail() throws Exception { * Test is ignored independent from others as it requires JDK 1.6 * @throws Exception */ - @Test(expected = IOException.class) + @Test public void testSNIFail() throws Exception { - Jsoup.connect(WEBSITE_WITH_SNI).execute(); + assertThrows(IOException.class, () -> Jsoup.connect(WEBSITE_WITH_SNI).execute()); } @Test diff --git a/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java b/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java index f131d6609d..13af91624e 100644 --- a/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java +++ b/src/test/java/org/jsoup/integration/servlets/Deflateservlet.java @@ -2,7 +2,6 @@ import org.jsoup.integration.TestServer; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/src/test/java/org/jsoup/integration/servlets/FileServlet.java b/src/test/java/org/jsoup/integration/servlets/FileServlet.java index 399c2e7afd..6249232ec7 100644 --- a/src/test/java/org/jsoup/integration/servlets/FileServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/FileServlet.java @@ -3,7 +3,6 @@ import org.jsoup.integration.ParseTest; import org.jsoup.integration.TestServer; -import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java index e680089422..7a7924c775 100644 --- a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java @@ -2,7 +2,6 @@ import org.jsoup.integration.TestServer; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java index 20084c49ce..5ab52a0bd5 100644 --- a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java @@ -2,11 +2,9 @@ import org.jsoup.integration.TestServer; -import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; public class RedirectServlet extends BaseServlet { public static final String Url = TestServer.map(RedirectServlet.class); @@ -36,7 +34,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) { } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException { + protected void doPost(HttpServletRequest req, HttpServletResponse res) { doGet(req, res); } } diff --git a/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java b/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java index fb84d9e1a5..6a7ad96103 100644 --- a/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java +++ b/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java @@ -1,18 +1,17 @@ package org.jsoup.internal; import org.jsoup.Jsoup; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.BufferedInputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; -@Ignore +@Disabled public class ConstrainableInputStreamTest { // todo - move these all to local jetty, don't ignore @@ -33,7 +32,7 @@ public void remainingAfterFirstRead() throws IOException { ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); byte[] array = firstBytes.array(); - String firstText = new String(array, "UTF-8"); + String firstText = new String(array, StandardCharsets.UTF_8); assertTrue(firstText.startsWith("<html><head><title>Large")); assertEquals(bufferSize, array.length); @@ -45,7 +44,7 @@ public void remainingAfterFirstRead() throws IOException { ByteBuffer fullRead = stream.readToByteBuffer(0); byte[] fullArray = fullRead.array(); assertEquals(capSize, fullArray.length); - String fullText = new String(fullArray, "UTF-8"); + String fullText = new String(fullArray, StandardCharsets.UTF_8); assertTrue(fullText.startsWith(firstText)); } @@ -63,7 +62,7 @@ public void noLimitAfterFirstRead() throws IOException { stream.mark(bufferSize); ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); byte[] array = firstBytes.array(); - String firstText = new String(array, "UTF-8"); + String firstText = new String(array, StandardCharsets.UTF_8); assertTrue(firstText.startsWith("<html><head><title>Large")); assertEquals(bufferSize, array.length); @@ -72,7 +71,7 @@ public void noLimitAfterFirstRead() throws IOException { ByteBuffer fullRead = stream.readToByteBuffer(0); byte[] fullArray = fullRead.array(); assertEquals(280735, fullArray.length); - String fullText = new String(fullArray, "UTF-8"); + String fullText = new String(fullArray, StandardCharsets.UTF_8); assertTrue(fullText.startsWith(firstText)); } diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index ca39f60efc..c674b97e6c 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -1,20 +1,19 @@ package org.jsoup.internal; import org.jsoup.Jsoup; -import org.jsoup.internal.StringUtil; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; -import static org.jsoup.internal.StringUtil.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.jsoup.internal.StringUtil.normaliseWhitespace; +import static org.jsoup.internal.StringUtil.resolve; +import static org.junit.jupiter.api.Assertions.*; public class StringUtilTest { - @Test public void join() { + @Test + public void join() { assertEquals("", StringUtil.join(Collections.singletonList(""), " ")); assertEquals("one", StringUtil.join(Collections.singletonList("one"), " ")); assertEquals("one two three", StringUtil.join(Arrays.asList("one", "two", "three"), " ")); diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index c8edfd0928..0ee1618557 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -1,12 +1,13 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AttributeTest { - @Test public void html() { + @Test + public void html() { Attribute attr = new Attribute("key", "value &"); assertEquals("key=\"value &amp;\"", attr.html()); assertEquals(attr.html(), attr.toString()); @@ -19,13 +20,15 @@ public class AttributeTest { assertEquals(attr.html(), attr.toString()); } - @Test(expected = IllegalArgumentException.class) public void validatesKeysNotEmpty() { - Attribute attr = new Attribute(" ", "Check"); + @Test public void validatesKeysNotEmpty() { + assertThrows(IllegalArgumentException.class, () -> new Attribute(" ", "Check")); } - @Test(expected = IllegalArgumentException.class) public void validatesKeysNotEmptyViaSet() { - Attribute attr = new Attribute("One", "Check"); - attr.setKey(" "); + @Test public void validatesKeysNotEmptyViaSet() { + assertThrows(IllegalArgumentException.class, () -> { + Attribute attr = new Attribute("One", "Check"); + attr.setKey(" "); + }); } @Test public void booleanAttributesAreEmptyStringValues() { diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 14872553df..84d9ed78c0 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -1,14 +1,12 @@ package org.jsoup.nodes; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Iterator; import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * Tests for Attributes. @@ -199,8 +197,8 @@ public void testSetKeyConsistency() { for(Attribute at : a) { at.setKey("b"); } - assertFalse("Attribute 'a' not correctly removed", a.hasKey("a")); - assertTrue("Attribute 'b' not present after renaming", a.hasKey("b")); + assertFalse(a.hasKey("a"), "Attribute 'a' not correctly removed"); + assertTrue(a.hasKey("b"), "Attribute 'b' not present after renaming"); } @Test diff --git a/src/test/java/org/jsoup/nodes/BuildEntities.java b/src/test/java/org/jsoup/nodes/BuildEntities.java index 58bb1fce66..423c26805a 100644 --- a/src/test/java/org/jsoup/nodes/BuildEntities.java +++ b/src/test/java/org/jsoup/nodes/BuildEntities.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.Map; @@ -49,14 +48,14 @@ public static void main(String[] args) throws IOException { } ref.name = name; } - Collections.sort(base, byName); - Collections.sort(full, byName); + base.sort(byName); + full.sort(byName); // now determine code point order ArrayList<CharacterRef> baseByCode = new ArrayList<>(base); ArrayList<CharacterRef> fullByCode = new ArrayList<>(full); - Collections.sort(baseByCode, byCode); - Collections.sort(fullByCode, byCode); + baseByCode.sort(byCode); + fullByCode.sort(byCode); // and update their codepoint index. @SuppressWarnings("unchecked") ArrayList<CharacterRef>[] codelists = new ArrayList[]{baseByCode, fullByCode}; diff --git a/src/test/java/org/jsoup/nodes/CommentTest.java b/src/test/java/org/jsoup/nodes/CommentTest.java index 59d99ffc03..fe1c136193 100644 --- a/src/test/java/org/jsoup/nodes/CommentTest.java +++ b/src/test/java/org/jsoup/nodes/CommentTest.java @@ -1,9 +1,9 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class CommentTest { private Comment comment = new Comment(" This is one heck of a comment! "); diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index e6eab62262..47177ea4f6 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -6,18 +6,14 @@ import org.jsoup.nodes.Document.OutputSettings; import org.jsoup.nodes.Document.OutputSettings.Syntax; import org.jsoup.select.Elements; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** Tests for Document. @@ -153,7 +149,7 @@ public class DocumentTest { } // Ignored since this test can take awhile to run. - @Ignore + @Disabled @Test public void testOverflowClone() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < 100000; i++) { @@ -419,16 +415,16 @@ public void testShiftJisRoundtrip() throws Exception { + "before&nbsp;after" + "</body>" + "</html>"; - InputStream is = new ByteArrayInputStream(input.getBytes(Charset.forName("ASCII"))); + InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.US_ASCII)); Document doc = Jsoup.parse(is, null, "http://example.com"); doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml); String output = new String(doc.html().getBytes(doc.outputSettings().charset()), doc.outputSettings().charset()); - assertFalse("Should not have contained a '?'.", output.contains("?")); - assertTrue("Should have contained a '&#xa0;' or a '&nbsp;'.", - output.contains("&#xa0;") || output.contains("&nbsp;")); + assertFalse(output.contains("?"), "Should not have contained a '?'."); + assertTrue(output.contains("&#xa0;") || output.contains("&nbsp;"), + "Should have contained a '&#xa0;' or a '&nbsp;'."); } @Test public void parseAndHtmlOnDifferentThreads() throws InterruptedException { @@ -440,12 +436,9 @@ public void testShiftJisRoundtrip() throws Exception { final Elements p = doc.select("p"); assertEquals(html, p.outerHtml()); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - out[0] = p.outerHtml(); - doc.outputSettings().charset(StandardCharsets.US_ASCII); - } + Thread thread = new Thread(() -> { + out[0] = p.outerHtml(); + doc.outputSettings().charset(StandardCharsets.US_ASCII); }); thread.start(); thread.join(); diff --git a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java index 443feb7542..afa5ae7f2e 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTypeTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTypeTest.java @@ -2,9 +2,10 @@ import org.jsoup.Jsoup; import org.jsoup.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Tests for the DocumentType node @@ -14,17 +15,17 @@ public class DocumentTypeTest { @Test public void constructorValidationOkWithBlankName() { - DocumentType fail = new DocumentType("","", ""); + new DocumentType("","", ""); } - @Test(expected = IllegalArgumentException.class) + @Test public void constructorValidationThrowsExceptionOnNulls() { - DocumentType fail = new DocumentType("html", null, null); + assertThrows(IllegalArgumentException.class, () -> new DocumentType("html", null, null)); } @Test public void constructorValidationOkWithBlankPublicAndSystemIds() { - DocumentType fail = new DocumentType("html","", ""); + new DocumentType("html","", ""); } @Test public void outerHtmlGeneration() { diff --git a/src/test/java/org/jsoup/nodes/ElementIT.java b/src/test/java/org/jsoup/nodes/ElementIT.java index bbd022c8a5..6b0add641e 100644 --- a/src/test/java/org/jsoup/nodes/ElementIT.java +++ b/src/test/java/org/jsoup/nodes/ElementIT.java @@ -1,12 +1,12 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ElementIT { @Test diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 88a412971d..74b3cb61cb 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -3,23 +3,13 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.parser.Tag; -import org.jsoup.select.Elements; -import org.jsoup.select.Evaluator; -import org.jsoup.select.NodeFilter; -import org.jsoup.select.NodeVisitor; -import org.jsoup.select.QueryParser; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import org.jsoup.select.*; +import org.junit.jupiter.api.Test; + +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests for Element (DOM stuff mostly). @@ -444,7 +434,7 @@ public class ElementTest { assertEquals("", div.attr("true")); List<Attribute> attributes = div.attributes().asList(); - assertEquals("There should be one attribute", 1, attributes.size()); + assertEquals(1, attributes.size(), "There should be one attribute"); assertFalse(div.hasAttr("false")); assertEquals("<div true></div>", div.outerHtml()); @@ -495,16 +485,20 @@ public class ElementTest { assertEquals("there &amp; now &gt; <p>Hello</p>", TextUtil.stripNewlines(div.html())); } - @Test(expected = IllegalArgumentException.class) public void testThrowsOnAddNullText() { - Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); - Element div = doc.getElementById("1"); - div.appendText(null); + @Test public void testThrowsOnAddNullText() { + assertThrows(IllegalArgumentException.class, () -> { + Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); + Element div = doc.getElementById("1"); + div.appendText(null); + }); } - @Test(expected = IllegalArgumentException.class) public void testThrowsOnPrependNullText() { - Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); - Element div = doc.getElementById("1"); - div.prependText(null); + @Test public void testThrowsOnPrependNullText() { + assertThrows(IllegalArgumentException.class, () -> { + Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); + Element div = doc.getElementById("1"); + div.prependText(null); + }); } @Test public void testAddNewHtml() { diff --git a/src/test/java/org/jsoup/nodes/EntitiesTest.java b/src/test/java/org/jsoup/nodes/EntitiesTest.java index 05ed5fbce7..a853f2125b 100644 --- a/src/test/java/org/jsoup/nodes/EntitiesTest.java +++ b/src/test/java/org/jsoup/nodes/EntitiesTest.java @@ -1,11 +1,11 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.jsoup.nodes.Document.OutputSettings; import static org.jsoup.nodes.Entities.EscapeMode.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; public class EntitiesTest { @Test public void escape() { diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index bcb060301f..a931efaf71 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -2,11 +2,11 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests for FormElement @@ -90,7 +90,7 @@ public class FormElementTest { boolean threw = false; try { - Connection con = form.submit(); + form.submit(); } catch (IllegalArgumentException e) { threw = true; assertEquals("Could not determine a form action URL for submit. Ensure you set a base URI when parsing.", diff --git a/src/test/java/org/jsoup/nodes/LeafNodeTest.java b/src/test/java/org/jsoup/nodes/LeafNodeTest.java index 5978fba676..0398ebcf73 100644 --- a/src/test/java/org/jsoup/nodes/LeafNodeTest.java +++ b/src/test/java/org/jsoup/nodes/LeafNodeTest.java @@ -3,9 +3,9 @@ import org.jsoup.Jsoup; import org.jsoup.select.Elements; import org.jsoup.select.NodeFilter; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class LeafNodeTest { diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 11bc796c86..1dd66f11ed 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -4,11 +4,12 @@ import org.jsoup.TextUtil; import org.jsoup.parser.Tag; import org.jsoup.select.NodeVisitor; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; + /** Tests Nodes @@ -298,22 +299,22 @@ public void tail(Node node, int depth) { inputElement.attr("value","bar"); - assertEquals(singletonAttributes("value", "bar"), getAttributesCaseInsensitive(inputElement, "value")); + assertEquals(singletonAttributes(), getAttributesCaseInsensitive(inputElement)); } - private Attributes getAttributesCaseInsensitive(Element element, String attributeName) { + private Attributes getAttributesCaseInsensitive(Element element) { Attributes matches = new Attributes(); for (Attribute attribute : element.attributes()) { - if (attribute.getKey().equalsIgnoreCase(attributeName)) { + if (attribute.getKey().equalsIgnoreCase("value")) { matches.put(attribute); } } return matches; } - private Attributes singletonAttributes(String key, String value) { + private Attributes singletonAttributes() { Attributes attributes = new Attributes(); - attributes.put(key, value); + attributes.put("value", "bar"); return attributes; } } diff --git a/src/test/java/org/jsoup/nodes/TextNodeTest.java b/src/test/java/org/jsoup/nodes/TextNodeTest.java index 535bfbcb56..18b78735d2 100644 --- a/src/test/java/org/jsoup/nodes/TextNodeTest.java +++ b/src/test/java/org/jsoup/nodes/TextNodeTest.java @@ -3,11 +3,11 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.internal.StringUtil; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** Test TextNodes diff --git a/src/test/java/org/jsoup/parser/AttributeParseTest.java b/src/test/java/org/jsoup/parser/AttributeParseTest.java index 4967c7bebe..d328b9fb40 100644 --- a/src/test/java/org/jsoup/parser/AttributeParseTest.java +++ b/src/test/java/org/jsoup/parser/AttributeParseTest.java @@ -1,16 +1,16 @@ package org.jsoup.parser; -import java.util.List; - import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** Test suite for attribute parser. @@ -82,7 +82,7 @@ public class AttributeParseTest { assertEquals("", el.attr("empty")); List<Attribute> attributes = el.attributes().asList(); - assertEquals("There should be 3 attribute present", 3, attributes.size()); + assertEquals(3, attributes.size(), "There should be 3 attribute present"); assertEquals(html, el.outerHtml()); // vets boolean syntax } @@ -90,7 +90,7 @@ public class AttributeParseTest { @Test public void dropsSlashFromAttributeName() { String html = "<img /onerror='doMyJob'/>"; Document doc = Jsoup.parse(html); - assertFalse("SelfClosingStartTag ignores last character", doc.select("img[onerror]").isEmpty()); + assertFalse(doc.select("img[onerror]").isEmpty(), "SelfClosingStartTag ignores last character"); assertEquals("<img onerror=\"doMyJob\">", doc.body().html()); doc = Jsoup.parse(html, "", Parser.xmlParser()); diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index 633aa4f96b..ba34d47a22 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -1,11 +1,11 @@ package org.jsoup.parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.BufferedReader; import java.io.StringReader; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Test suite for character reader. @@ -295,7 +295,7 @@ public void consumeToNonexistentEndWhenAtAnd() { public void notEmptyAtBufferSplitPoint() { CharacterReader r = new CharacterReader(new StringReader("How about now"), 3); assertEquals("How", r.consumeTo(' ')); - assertFalse("Should not be empty", r.isEmpty()); + assertFalse(r.isEmpty(), "Should not be empty"); assertEquals(' ', r.consume()); assertFalse(r.isEmpty()); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 6e8302549f..000ff97888 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -2,21 +2,13 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; -import org.jsoup.internal.StringUtil; import org.jsoup.integration.ParseTest; -import org.jsoup.nodes.CDataNode; -import org.jsoup.nodes.Comment; -import org.jsoup.nodes.DataNode; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.Entities; -import org.jsoup.nodes.FormElement; -import org.jsoup.nodes.Node; -import org.jsoup.nodes.TextNode; +import org.jsoup.internal.StringUtil; +import org.jsoup.nodes.*; import org.jsoup.safety.Whitelist; import org.jsoup.select.Elements; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.File; @@ -24,7 +16,7 @@ import java.util.List; import static org.jsoup.parser.ParseSettings.preserveCase; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests for the Parser @@ -634,7 +626,7 @@ public class HtmlParserTest { assertEquals("<b>1</b>\n<p><b>2</b>3</p>", doc.body().html()); } - @Ignore // todo: test case for https://github.com/jhy/jsoup/issues/845. Doesn't work yet. + @Disabled // todo: test case for https://github.com/jhy/jsoup/issues/845. Doesn't work yet. @Test public void handlesMisnestedAInDivs() { String h = "<a href='#1'><div><div><a href='#2'>child</a</div</div></a>"; String w = "<a href=\"#1\"></a><div><a href=\"#1\"></a><div><a href=\"#1\"></a><a href=\"#2\">child</a></div></div>"; @@ -915,7 +907,7 @@ public class HtmlParserTest { Document doc = Jsoup.parse(html); Element el = doc.select("form").first(); - assertTrue("Is form element", el instanceof FormElement); + assertTrue(el instanceof FormElement, "Is form element"); FormElement form = (FormElement) el; Elements controls = form.elements(); assertEquals(2, controls.size()); @@ -929,7 +921,7 @@ public class HtmlParserTest { Document doc = Jsoup.parse(html); Element el = doc.select("form").first(); - assertTrue("Is form element", el instanceof FormElement); + assertTrue(el instanceof FormElement, "Is form element"); FormElement form = (FormElement) el; Elements controls = form.elements(); assertEquals(2, controls.size()); @@ -1000,9 +992,9 @@ public void testInvalidTableContents() throws IOException { String rendered = doc.toString(); int endOfEmail = rendered.indexOf("Comment"); int guarantee = rendered.indexOf("Why am I here?"); - assertTrue("Comment not found", endOfEmail > -1); - assertTrue("Search text not found", guarantee > -1); - assertTrue("Search text did not come after comment", guarantee > endOfEmail); + assertTrue(endOfEmail > -1, "Comment not found"); + assertTrue(guarantee > -1, "Search text not found"); + assertTrue(guarantee > endOfEmail, "Search text did not come after comment"); } @Test public void testNormalisesIsIndex() { diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 1836e409b2..97bd5287a5 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -1,7 +1,7 @@ package org.jsoup.parser; import org.jsoup.parser.HtmlTreeBuilderState.Constants; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -9,14 +9,13 @@ import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class HtmlTreeBuilderStateTest { - static List<Object[]> findArrays(Class<?> constantClass) { + static List<Object[]> findArrays() { ArrayList<Object[]> array = new ArrayList<>(); - - Field[] fields = constantClass.getDeclaredFields(); + Field[] fields = Constants.class.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) && field.getType().isArray()) { @@ -41,7 +40,7 @@ static void ensureSorted(List<Object[]> constants) { @Test public void ensureArraysAreSorted() { - List<Object[]> constants = findArrays(Constants.class); + List<Object[]> constants = findArrays(); ensureSorted(constants); assertEquals(38, constants.size()); } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java index 3753376873..04630dc990 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java @@ -1,10 +1,11 @@ package org.jsoup.parser; -import org.junit.Test; + +import org.junit.jupiter.api.Test; import java.util.Arrays; -import static org.junit.Assert.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; public class HtmlTreeBuilderTest { @Test diff --git a/src/test/java/org/jsoup/parser/ParserIT.java b/src/test/java/org/jsoup/parser/ParserIT.java index a4c5173f1a..e5dee55b1d 100644 --- a/src/test/java/org/jsoup/parser/ParserIT.java +++ b/src/test/java/org/jsoup/parser/ParserIT.java @@ -1,10 +1,10 @@ package org.jsoup.parser; import org.jsoup.nodes.Document; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Longer running Parser tests. diff --git a/src/test/java/org/jsoup/parser/ParserSettingsTest.java b/src/test/java/org/jsoup/parser/ParserSettingsTest.java index a4903d6b82..7856287cb1 100644 --- a/src/test/java/org/jsoup/parser/ParserSettingsTest.java +++ b/src/test/java/org/jsoup/parser/ParserSettingsTest.java @@ -1,17 +1,17 @@ package org.jsoup.parser; -import org.jsoup.MultiLocaleRule; -import org.jsoup.MultiLocaleRule.MultiLocaleTest; +import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.nodes.Attributes; -import org.junit.Rule; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class ParserSettingsTest { - @Rule public MultiLocaleRule rule = new MultiLocaleRule(); + @MultiLocaleTest + public void caseSupport(Locale locale) { + Locale.setDefault(locale); - @Test @MultiLocaleTest public void caseSupport() { ParseSettings bothOn = new ParseSettings(true, true); ParseSettings bothOff = new ParseSettings(false, false); ParseSettings tagOn = new ParseSettings(true, false); @@ -30,15 +30,20 @@ public class ParserSettingsTest { assertEquals("ID", attrOn.normalizeAttribute("ID")); } - @Test @MultiLocaleTest public void attributeCaseNormalization() { - ParseSettings parseSettings = new ParseSettings(false, false); + @MultiLocaleTest + public void attributeCaseNormalization(Locale locale) { + Locale.setDefault(locale); + ParseSettings parseSettings = new ParseSettings(false, false); String normalizedAttribute = parseSettings.normalizeAttribute("HIDDEN"); assertEquals("hidden", normalizedAttribute); } - @Test @MultiLocaleTest public void attributesCaseNormalization() { + @MultiLocaleTest + public void attributesCaseNormalization(Locale locale) { + Locale.setDefault(locale); + ParseSettings parseSettings = new ParseSettings(false, false); Attributes attributes = new Attributes(); attributes.put("ITEM", "1"); diff --git a/src/test/java/org/jsoup/parser/ParserTest.java b/src/test/java/org/jsoup/parser/ParserTest.java index 6ab9e70377..f77b230616 100644 --- a/src/test/java/org/jsoup/parser/ParserTest.java +++ b/src/test/java/org/jsoup/parser/ParserTest.java @@ -1,8 +1,8 @@ package org.jsoup.parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class ParserTest { diff --git a/src/test/java/org/jsoup/parser/TagTest.java b/src/test/java/org/jsoup/parser/TagTest.java index d05751ac29..65a794d847 100644 --- a/src/test/java/org/jsoup/parser/TagTest.java +++ b/src/test/java/org/jsoup/parser/TagTest.java @@ -1,25 +1,26 @@ package org.jsoup.parser; -import org.jsoup.MultiLocaleRule; -import org.jsoup.MultiLocaleRule.MultiLocaleTest; -import org.junit.Rule; -import org.junit.Test; +import org.jsoup.MultiLocaleExtension.MultiLocaleTest; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.*; /** Tag tests. @author Jonathan Hedley, jonathan@hedley.net */ public class TagTest { - @Rule public MultiLocaleRule rule = new MultiLocaleRule(); - @Test public void isCaseSensitive() { Tag p1 = Tag.valueOf("P"); Tag p2 = Tag.valueOf("p"); assertNotEquals(p1, p2); } - @Test @MultiLocaleTest public void canBeInsensitive() { + @MultiLocaleTest + public void canBeInsensitive(Locale locale) { + Locale.setDefault(locale); + Tag script1 = Tag.valueOf("script", ParseSettings.htmlDefault); Tag script2 = Tag.valueOf("SCRIPT", ParseSettings.htmlDefault); assertSame(script1, script2); @@ -68,12 +69,12 @@ public class TagTest { assertTrue(foo.formatAsBlock()); } - @Test(expected = IllegalArgumentException.class) public void valueOfChecksNotNull() { - Tag.valueOf(null); + @Test public void valueOfChecksNotNull() { + assertThrows(IllegalArgumentException.class, () -> Tag.valueOf(null)); } - @Test(expected = IllegalArgumentException.class) public void valueOfChecksNotEmpty() { - Tag.valueOf(" "); + @Test public void valueOfChecksNotEmpty() { + assertThrows(IllegalArgumentException.class, () -> Tag.valueOf(" ")); } @Test public void knownTags() { diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java index 9f039a6a6e..2690c4bf97 100644 --- a/src/test/java/org/jsoup/parser/TokenQueueTest.java +++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java @@ -1,9 +1,10 @@ package org.jsoup.parser; import org.jsoup.Jsoup; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * Token queue tests. @@ -89,7 +90,7 @@ public void chompBalancedThrowIllegalArgumentException() { TokenQueue tq = new TokenQueue("unbalanced(something(or another)) else"); tq.consumeTo("("); tq.chompBalanced('(', '+'); - org.junit.Assert.fail("should have thrown IllegalArgumentException"); + fail("should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { assertEquals("Did not find balanced marker at 'something(or another)) else'", expected.getMessage()); } diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index bc4280b64a..d8a791f388 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -7,12 +7,12 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class TokeniserStateTest { @@ -141,7 +141,7 @@ public void testPublicIdentifiersWithWhitespace() { String expectedOutput = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0//EN\">"; for (char q : quote) { for (char ws : whiteSpace) { - String[] htmls = { + String[] htmls = { String.format("<!DOCTYPE html%cPUBLIC %c-//W3C//DTD HTML 4.0//EN%c>", ws, q, q), String.format("<!DOCTYPE html %cPUBLIC %c-//W3C//DTD HTML 4.0//EN%c>", ws, q, q), String.format("<!DOCTYPE html PUBLIC%c%c-//W3C//DTD HTML 4.0//EN%c>", ws, q, q), @@ -210,7 +210,7 @@ public void testUnconsumeAtBufferBoundary() { String triggeringSnippet = "<a href=\"\"foo"; char[] padding = new char[CharacterReader.readAheadLimit - triggeringSnippet.length() + 2]; // The "foo" part must be just at the limit. Arrays.fill(padding, ' '); - String paddedSnippet = new StringBuilder().append(padding).append(triggeringSnippet).toString(); + String paddedSnippet = String.valueOf(padding) + triggeringSnippet; ParseErrorList errorList = ParseErrorList.tracking(1); Parser.parseFragment(paddedSnippet, null, "", errorList); diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index ca80e30823..d7a6b10936 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -1,23 +1,15 @@ package org.jsoup.parser; -import static org.jsoup.parser.CharacterReader.maxBufferLen; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import org.jsoup.Jsoup; +import org.jsoup.nodes.*; +import org.jsoup.select.Elements; +import org.junit.jupiter.api.Test; import java.io.UnsupportedEncodingException; import java.util.Arrays; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Attribute; -import org.jsoup.nodes.CDataNode; -import org.jsoup.nodes.Comment; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; -import org.jsoup.nodes.TextNode; -import org.jsoup.select.Elements; -import org.junit.Test; +import static org.jsoup.parser.CharacterReader.maxBufferLen; +import static org.junit.jupiter.api.Assertions.*; public class TokeniserTest { @Test @@ -43,7 +35,7 @@ public void bufferUpInAttributeVal() { Document doc = Jsoup.parse(html); String src = doc.select("img").attr("src"); - assertTrue("Handles for quote " + quote, src.contains("X")); + assertTrue(src.contains("X"), "Handles for quote " + quote); assertTrue(src.contains(tail)); } } @@ -181,7 +173,7 @@ public void bufferUpInAttributeVal() { // some of these characters are illegal if (s.charAt(0) == '\ufffd') { continue; } - assertEquals("At: " + i, s.charAt(0), Tokeniser.win1252Extensions[i]); + assertEquals(s.charAt(0), Tokeniser.win1252Extensions[i], "At: " + i); } } @@ -197,7 +189,7 @@ public void bufferUpInAttributeVal() { Document doc = parser.parseInput(testMarkup, ""); Node commentNode = doc.body().childNode(0); - assertTrue("Expected comment node", commentNode instanceof Comment); + assertTrue(commentNode instanceof Comment, "Expected comment node"); assertEquals(expectedCommentData, ((Comment)commentNode).getData()); } @@ -214,7 +206,7 @@ public void bufferUpInAttributeVal() { Document doc = parser.parseInput(testMarkup, ""); Node cdataNode = doc.body().childNode(0); - assertTrue("Expected CDATA node", cdataNode instanceof CDataNode); + assertTrue(cdataNode instanceof CDataNode, "Expected CDATA node"); assertEquals(cdataContents, ((CDataNode)cdataNode).text()); } } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 5dd18d85b6..8aa7547a27 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -3,27 +3,21 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.internal.StringUtil; -import org.jsoup.nodes.CDataNode; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; -import org.jsoup.nodes.TextNode; -import org.jsoup.nodes.XmlDeclaration; +import org.jsoup.nodes.*; import org.jsoup.select.Elements; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; import static org.jsoup.nodes.Document.OutputSettings.Syntax; -import static org.junit.Assert.*; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.*; /** * Tests XmlTreeBuilder. @@ -68,7 +62,7 @@ public void testSupplyParserToJsoupClass() { TextUtil.stripNewlines(doc.html())); } - @Ignore + @Disabled @Test public void testSupplyParserToConnection() throws IOException { String xmlUrl = "http://direct.infohound.net/tools/jsoup-xml-test.xml"; @@ -170,7 +164,7 @@ public void caseSensitiveDeclaration() { public void testCreatesValidProlog() { Document document = Document.createShell(""); document.outputSettings().syntax(Syntax.xml); - document.charset(Charset.forName("utf-8")); + document.charset(StandardCharsets.UTF_8); assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 26ad288841..707313f8f4 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -1,30 +1,28 @@ package org.jsoup.safety; -import org.jsoup.MultiLocaleRule; -import org.jsoup.MultiLocaleRule.MultiLocaleTest; import org.jsoup.Jsoup; +import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.TextUtil; import org.jsoup.nodes.Document; import org.jsoup.nodes.Entities; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.*; /** Tests for the cleaner. @author Jonathan Hedley, jonathan@hedley.net */ public class CleanerTest { - @Rule public MultiLocaleRule rule = new MultiLocaleRule(); - @Test public void simpleBehaviourTest() { String h = "<div><p class=foo><a href='http://evil.com'>Hello <b id=bar>there</b>!</a></div>"; String cleanHtml = Jsoup.clean(h, Whitelist.simpleText()); assertEquals("Hello <b>there</b>!", TextUtil.stripNewlines(cleanHtml)); } - + @Test public void simpleBehaviourTest2() { String h = "Hello <b>there</b>!"; String cleanHtml = Jsoup.clean(h, Whitelist.simpleText()); @@ -39,13 +37,13 @@ public class CleanerTest { assertEquals("<p><a rel=\"nofollow\">Dodgy</a> <a href=\"http://nice.com\" rel=\"nofollow\">Nice</a></p><blockquote>Hello</blockquote>", TextUtil.stripNewlines(cleanHtml)); } - + @Test public void basicWithImagesTest() { String h = "<div><p><img src='http://example.com/' alt=Image></p><p><img src='ftp://ftp.example.com'></p></div>"; String cleanHtml = Jsoup.clean(h, Whitelist.basicWithImages()); assertEquals("<p><img src=\"http://example.com/\" alt=\"Image\"></p><p><img></p>", TextUtil.stripNewlines(cleanHtml)); } - + @Test public void testRelaxed() { String h = "<h1>Head</h1><table><tr><td>One<td>Two</td></tr></table>"; String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); @@ -82,7 +80,10 @@ public class CleanerTest { TextUtil.stripNewlines(cleanHtml)); } - @Test @MultiLocaleTest public void whitelistedProtocolShouldBeRetained() { + @MultiLocaleTest + public void whitelistedProtocolShouldBeRetained(Locale locale) { + Locale.setDefault(locale); + Whitelist whitelist = Whitelist.none() .addTags("a") .addAttributes("a", "href") @@ -98,25 +99,25 @@ public class CleanerTest { String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); assertEquals("<p>Hello</p>", cleanHtml); } - + @Test public void testDropXmlProc() { String h = "<?import namespace=\"xss\"><p>Hello</p>"; String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); assertEquals("<p>Hello</p>", cleanHtml); } - + @Test public void testDropScript() { String h = "<SCRIPT SRC=//ha.ckers.org/.j><SCRIPT>alert(/XSS/.source)</SCRIPT>"; String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); assertEquals("", cleanHtml); } - + @Test public void testDropImageScript() { String h = "<IMG SRC=\"javascript:alert('XSS')\">"; String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); assertEquals("<img>", cleanHtml); } - + @Test public void testCleanJavascriptHref() { String h = "<A HREF=\"javascript:document.location='http://www.google.com/'\">XSS</A>"; String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); @@ -150,7 +151,7 @@ public class CleanerTest { String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); assertEquals("<p>Test</p>", cleanHtml); } - + @Test public void testHandlesEmptyAttributes() { String h = "<img alt=\"\" src= unknown=''>"; String cleanHtml = Jsoup.clean(h, Whitelist.basicWithImages()); @@ -190,7 +191,7 @@ public class CleanerTest { assertFalse(cleaner.isValid(Jsoup.parse(nok))); assertFalse(new Cleaner(Whitelist.none()).isValid(okDoc)); } - + @Test public void resolvesRelativeLinks() { String html = "<a href='/foo'>Link</a><img src='/bar'>"; String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages()); @@ -202,7 +203,7 @@ public class CleanerTest { String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages().preserveRelativeLinks(true)); assertEquals("<a href=\"/foo\" rel=\"nofollow\">Link</a>\n<img src=\"/bar\"> \n<img>", clean); } - + @Test public void dropsUnresolvableRelativeLinks() { String html = "<a href='/foo'>Link</a>"; String clean = Jsoup.clean(html, Whitelist.basic()); @@ -283,14 +284,16 @@ public void testScriptTagInWhiteList() { assertTrue( Jsoup.isValid("Hello<script>alert('Doh')</script>World !", whitelist ) ); } - @Test(expected = IllegalArgumentException.class) + @Test public void bailsIfRemovingProtocolThatsNotSet() { - // a case that came up on the email list - Whitelist w = Whitelist.none(); - - // note no add tag, and removing protocol without adding first - w.addAttributes("a", "href"); - w.removeProtocols("a", "href", "javascript"); // with no protocols enforced, this was a noop. Now validates. + assertThrows(IllegalArgumentException.class, () -> { + // a case that came up on the email list + Whitelist w = Whitelist.none(); + + // note no add tag, and removing protocol without adding first + w.addAttributes("a", "href"); + w.removeProtocols("a", "href", "javascript"); // with no protocols enforced, this was a noop. Now validates. + }); } @Test public void handlesControlCharactersAfterTagName() { diff --git a/src/test/java/org/jsoup/select/CssTest.java b/src/test/java/org/jsoup/select/CssTest.java index 746e8d368b..956d7f126b 100644 --- a/src/test/java/org/jsoup/select/CssTest.java +++ b/src/test/java/org/jsoup/select/CssTest.java @@ -1,23 +1,24 @@ package org.jsoup.select; -import static org.junit.Assert.*; - import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.parser.Tag; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + public class CssTest { private Document html = null; private static String htmlString; - - @BeforeClass + + @BeforeAll public static void initClass() { StringBuilder sb = new StringBuilder("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>"); - + sb.append("<div id='pseudo'>"); for (int i = 1; i <= 10; i++) { sb.append(String.format("<p>%d</p>",i)); @@ -35,20 +36,20 @@ public static void initClass() { sb.append("<span id='onlySpan'><br /></span>"); sb.append("<p class='empty'><!-- Comment only is still empty! --></p>"); - + sb.append("<div id='only'>"); sb.append("Some text before the <em>only</em> child in this div"); sb.append("</div>"); - + sb.append("</body></html>"); htmlString = sb.toString(); } - @Before + @BeforeEach public void init() { html = Jsoup.parse(htmlString); } - + @Test public void firstChild() { check(html.select("#pseudo :first-child"), "1"); @@ -60,7 +61,7 @@ public void lastChild() { check(html.select("#pseudo :last-child"), "10"); check(html.select("html:last-child")); } - + @Test public void nthChild_simple() { for(int i = 1; i <=10; i++) { @@ -88,7 +89,7 @@ public void nthOfType_simple() { check(html.select(String.format("#type p:nth-of-type(%d)", i)), String.valueOf(i)); } } - + @Test public void nthLastOfType_simple() { for(int i = 1; i <=10; i++) { @@ -124,7 +125,7 @@ public void nthOfType_advanced() { check(html.select("#type :nth-of-type(+5)"), "5", "5", "5", "5"); } - + @Test public void nthLastChild_advanced() { check(html.select("#pseudo :nth-last-child(-5)")); @@ -154,7 +155,7 @@ public void nthLastOfType_advanced() { check(html.select("#type span:nth-last-of-type(-2n+5)"), "6", "8", "10"); check(html.select("#type :nth-last-of-type(+5)"), "6", "6", "6", "6"); } - + @Test public void firstOfType() { check(html.select("div:not(#only) :first-of-type"), "1", "1", "1", "1", "1"); @@ -173,16 +174,16 @@ public void empty() { assertEquals("br", sel.get(1).tagName()); assertEquals("p", sel.get(2).tagName()); } - + @Test public void onlyChild() { final Elements sel = html.select("span :only-child"); assertEquals(1, sel.size()); assertEquals("br", sel.get(0).tagName()); - + check(html.select("#only :only-child"), "only"); } - + @Test public void onlyOfType() { final Elements sel = html.select(":only-of-type"); @@ -195,16 +196,15 @@ public void onlyOfType() { assertTrue(sel.get(4).hasClass("empty")); assertEquals("em", sel.get(5).tagName()); } - + protected void check(Elements result, String...expectedContent ) { - assertEquals("Number of elements", expectedContent.length, result.size()); + assertEquals(expectedContent.length, result.size(), "Number of elements"); for (int i = 0; i < expectedContent.length; i++) { assertNotNull(result.get(i)); - assertEquals("Expected element",expectedContent[i], result.get(i).ownText()); + assertEquals(expectedContent[i], result.get(i).ownText(), "Expected element"); } } - @Test public void root() { Elements sel = html.select(":root"); diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index e7244df99a..c41a01b9aa 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -2,18 +2,12 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; -import org.jsoup.nodes.Comment; -import org.jsoup.nodes.DataNode; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.FormElement; -import org.jsoup.nodes.Node; -import org.jsoup.nodes.TextNode; -import org.junit.Test; +import org.jsoup.nodes.*; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** Tests for ElementList. diff --git a/src/test/java/org/jsoup/select/QueryParserTest.java b/src/test/java/org/jsoup/select/QueryParserTest.java index f2ac60cf8a..95c4da16a2 100644 --- a/src/test/java/org/jsoup/select/QueryParserTest.java +++ b/src/test/java/org/jsoup/select/QueryParserTest.java @@ -1,7 +1,8 @@ package org.jsoup.select; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests for the Selector Query Parser. @@ -40,23 +41,21 @@ public class QueryParserTest { assertEquals(2, andLeft.evaluators.size()); } - @Test(expected = Selector.SelectorParseException.class) public void exceptionOnUncloseAttribute() { - Evaluator parse = QueryParser.parse("section > a[href=\"]"); + @Test public void exceptionOnUncloseAttribute() { + assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse("section > a[href=\"]")); } - @Test(expected = Selector.SelectorParseException.class) public void testParsesSingleQuoteInContains() { - Evaluator parse = QueryParser.parse("p:contains(One \" One)"); + @Test public void testParsesSingleQuoteInContains() { + assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse("p:contains(One \" One)")); } - @Test(expected = Selector.SelectorParseException.class) - public void exceptOnEmptySelector() { - Evaluator parse = QueryParser.parse(""); + @Test public void exceptOnEmptySelector() { + assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse("")); } - @Test(expected = Selector.SelectorParseException.class) - public void exceptOnNullSelector() { - Evaluator parse = QueryParser.parse(null); + @Test public void exceptOnNullSelector() { + assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse(null)); } @Test public void okOnSpacesForeAndAft() { diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 7720b254c0..347f80cc06 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1,15 +1,15 @@ package org.jsoup.select; import org.jsoup.Jsoup; -import org.jsoup.MultiLocaleRule; -import org.jsoup.MultiLocaleRule.MultiLocaleTest; +import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.parser.Parser; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests that the selector selects correctly. @@ -17,8 +17,6 @@ * @author Jonathan Hedley, jonathan@hedley.net */ public class SelectorTest { - @Rule public MultiLocaleRule rule = new MultiLocaleRule(); - @Test public void testByTag() { // should be case insensitive Elements els = Jsoup.parse("<div id=1><div id=2><p>Hello</p></div></div><DIV id=3>").select("DIV"); @@ -64,7 +62,11 @@ public class SelectorTest { assertEquals("Two", elsFromClass.get(1).text()); } - @Test @MultiLocaleTest public void testByAttribute() { + + @MultiLocaleTest + public void testByAttribute(Locale locale) { + Locale.setDefault(locale); + String h = "<div Title=Foo /><div Title=Bar /><div Style=Qux /><div title=Balim /><div title=SLIM />" + "<div data-name='with spaces'/>"; Document doc = Jsoup.parse(h); @@ -187,7 +189,10 @@ public class SelectorTest { assertEquals(1, els2.size()); } - @Test @MultiLocaleTest public void testByAttributeStarting() { + @MultiLocaleTest + public void testByAttributeStarting(Locale locale) { + Locale.setDefault(locale); + Document doc = Jsoup.parse("<div id=1 ATTRIBUTE data-name=jsoup>Hello</div><p data-val=5 id=2>There</p><p id=3>No</p>"); Elements withData = doc.select("[^data-]"); assertEquals(2, withData.size()); @@ -281,7 +286,7 @@ public class SelectorTest { String h = "<div class=head><p class=first>Hello</p><p>There</p></div><p>None</p>"; Document doc = Jsoup.parse(h); Element root = doc.getElementsByClass("HEAD").first(); - + Elements els = root.select(".head p"); assertEquals(2, els.size()); assertEquals("Hello", els.get(0).text()); @@ -293,7 +298,7 @@ public class SelectorTest { Elements empty = root.select("p .first"); // self, not descend, should not match assertEquals(0, empty.size()); - + Elements aboveRoot = root.select("body div.head"); assertEquals(0, aboveRoot.size()); } @@ -571,7 +576,10 @@ public class SelectorTest { assertEquals("Two", divs.first().text()); } - @Test @MultiLocaleTest public void testPseudoContains() { + @MultiLocaleTest + public void testPseudoContains(Locale locale) { + Locale.setDefault(locale); + Document doc = Jsoup.parse("<div><p>The Rain.</p> <p class=light>The <i>RAIN</i>.</p> <p>Rain, the.</p></div>"); Elements ps1 = doc.select("p:contains(Rain)"); @@ -609,7 +617,10 @@ public class SelectorTest { assertEquals("2", ps2.first().id()); } - @Test @MultiLocaleTest public void containsOwn() { + @MultiLocaleTest + public void containsOwn(Locale locale) { + Locale.setDefault(locale); + Document doc = Jsoup.parse("<p id=1>Hello <b>there</b> igor</p>"); Elements ps = doc.select("p:containsOwn(Hello IGOR)"); assertEquals(1, ps.size()); @@ -721,24 +732,24 @@ public class SelectorTest { assertEquals("div", doc.select("div[k" + s + "]").first().tagName()); assertEquals("div", doc.select("div:containsOwn(" + s + ")").first().tagName()); } - + @Test public void selectClassWithSpace() { final String html = "<div class=\"value\">class without space</div>\n" + "<div class=\"value \">class with space</div>"; - + Document doc = Jsoup.parse(html); - + Elements found = doc.select("div[class=value ]"); assertEquals(2, found.size()); assertEquals("class without space", found.get(0).text()); assertEquals("class with space", found.get(1).text()); - + found = doc.select("div[class=\"value \"]"); assertEquals(2, found.size()); assertEquals("class without space", found.get(0).text()); assertEquals("class with space", found.get(1).text()); - + found = doc.select("div[class=\"value\\ \"]"); assertEquals(0, found.size()); } @@ -763,7 +774,10 @@ public void selectClassWithSpace() { assertEquals("Two", doc.select("div[data=\"[Another)]]\"]").first().text()); } - @Test @MultiLocaleTest public void containsData() { + @MultiLocaleTest + public void containsData(Locale locale) { + Locale.setDefault(locale); + String html = "<p>function</p><script>FUNCTION</script><style>item</style><span><!-- comments --></span>"; Document doc = Jsoup.parse(html); Element body = doc.body(); diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java index c8af364847..00f70d32ac 100644 --- a/src/test/java/org/jsoup/select/TraversorTest.java +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -1,11 +1,11 @@ package org.jsoup.select; -import static org.junit.Assert.assertEquals; - import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Node; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class TraversorTest { // Note: NodeTraversor.traverse(new NodeVisitor) is tested in From 6b103e1e9d4bfcef5a10bcb7be5c827894bd75ce Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 5 Mar 2020 10:28:42 -0800 Subject: [PATCH 434/774] Updated min Android from 8 to 10. And changenote for #1335 --- CHANGES | 6 +++++- pom.xml | 6 +++--- src/main/java/org/jsoup/nodes/Attributes.java | 16 ++++------------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 31e1bfa7e2..44bc0194a5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog -*** Release 1.13.2 [PENDING] +*** Release 1.14.1 [PENDING] + * Change: updated the minimum supported Java version from Java 7 to Java 8. + + * Change: updated the minimum Android API level from 8 to 10. + * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). *** Release 1.13.1 [2020-Feb-29] diff --git a/pom.xml b/pom.xml index b691ec7f03..f125b7e89b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.13.2-SNAPSHOT</version> + <version>1.14.1-SNAPSHOT</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -69,8 +69,8 @@ </signature> <signature> <groupId>net.sf.androidscents.signature</groupId> - <artifactId>android-api-level-8</artifactId> - <version>2.2_r3</version> + <artifactId>android-api-level-10</artifactId> + <version>2.3.3_r2</version> </signature> </configuration> </execution> diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 2d7def8c35..0f22f36cbf 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -59,16 +59,8 @@ private void checkCapacity(int minNewSize) { if (minNewSize > newSize) newSize = minNewSize; - keys = copyOf(keys, newSize); - vals = copyOf(vals, newSize); - } - - // simple implementation of Arrays.copy, for support of Android API 8. - private static String[] copyOf(String[] orig, int size) { - final String[] copy = new String[size]; - System.arraycopy(orig, 0, copy, 0, - Math.min(orig.length, size)); - return copy; + keys = Arrays.copyOf(keys, newSize); + vals = Arrays.copyOf(vals, newSize); } int indexOfKey(String key) { @@ -418,8 +410,8 @@ public Attributes clone() { throw new RuntimeException(e); } clone.size = size; - keys = copyOf(keys, size); - vals = copyOf(vals, size); + keys = Arrays.copyOf(keys, size); + vals = Arrays.copyOf(vals, size); return clone; } From 23a0e22ede7a83c7c8ea61d7877be414c2f9abf1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 5 Mar 2020 10:30:20 -0800 Subject: [PATCH 435/774] Missed test annotation #1335 --- src/test/java/org/jsoup/nodes/ElementTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 74b3cb61cb..b22e66ecb4 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1590,6 +1590,7 @@ public void testReparentSeperateNodes() { assertEquals("<p>Four</p><p>Three</p><p>Four</p><div><p>One</p><p>Two</p></div><p>Three</p>", TextUtil.stripNewlines(doc.body().html())); } + @Test public void testChildSizeWithMixedContent() { Document doc = Jsoup.parse("<table><tbody>\n<tr>\n<td>15:00</td>\n<td>sport</td>\n</tr>\n</tbody></table>"); Element row = doc.selectFirst("table tbody tr"); From 89580cc3d25d0d89ac1f46b349e5cd315883dc79 Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Sat, 7 Mar 2020 19:11:06 +0000 Subject: [PATCH 436/774] JUnit 5 dependency aggregator. (#1336) --- pom.xml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index f125b7e89b..3b6fdb595e 100644 --- a/pom.xml +++ b/pom.xml @@ -259,19 +259,7 @@ <!-- junit --> <dependency> <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <version>5.6.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <version>5.6.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-params</artifactId> + <artifactId>junit-jupiter</artifactId> <version>5.6.0</version> <scope>test</scope> </dependency> From 653d5e7c832b33b793d110b0feea549a2604eb65 Mon Sep 17 00:00:00 2001 From: Carl Dea <carl.dea@gmail.com> Date: Wed, 9 Dec 2020 23:13:48 -0500 Subject: [PATCH 437/774] Test on Java versions on Linux, Windows and MacOS. (#1458) Signed-off-by: Carl Dea <carl.dea@gmail.com> --- .github/workflows/test-java-versions.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/test-java-versions.yml diff --git a/.github/workflows/test-java-versions.yml b/.github/workflows/test-java-versions.yml new file mode 100644 index 0000000000..2b8fb5a655 --- /dev/null +++ b/.github/workflows/test-java-versions.yml @@ -0,0 +1,22 @@ +on: [push] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + java: [8, 9, 11, 12, 13, 15, 16-ea] + fail-fast: false + max-parallel: 4 + name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Verify with Maven + run: mvn -X verify -B --file pom.xml +... From 6dba1650712043c8292e67a68bbc9493247d61e8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 10 Dec 2020 15:57:29 +1100 Subject: [PATCH 438/774] Move from Travis to GitHub Actions (#1459) * Clean up move from Travis to GitHub Workflow --- .../{test-java-versions.yml => build.yml} | 1 + .travis.yml | 39 ------------------- CHANGES | 6 ++- README.md | 4 +- 4 files changed, 7 insertions(+), 43 deletions(-) rename .github/workflows/{test-java-versions.yml => build.yml} (97%) delete mode 100644 .travis.yml diff --git a/.github/workflows/test-java-versions.yml b/.github/workflows/build.yml similarity index 97% rename from .github/workflows/test-java-versions.yml rename to .github/workflows/build.yml index 2b8fb5a655..18394b44ea 100644 --- a/.github/workflows/test-java-versions.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,4 @@ +name: Build on: [push] jobs: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 988bd1b80d..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -# From https://github.com/DanySK/Gravis-CI - -language: bash -dist: xenial -os: - - linux - -env: - global: - - GRAVIS="https://raw.githubusercontent.com/DanySK/Gravis-CI/master/" - - matrix: - # List any JDK you want to build your software with. - # You can see the list of supported environments by installing Jabba and using ls-remote: - # https://github.com/shyiko/jabba#usage - - JDK="adopt@1.8." - - JDK="amazon-corretto@1.8." - - JDK="openjdk@1.9." - - JDK="openjdk@1.11." - - JDK="openjdk@1.12." - - JDK="openjdk@1.13." - -cache: - directories: - # Avoid re-downloading the JDK every time - - $HOME/.jabba/ - - $HOME/.m2/ - -before_install: - # Sets up the Java environment - - curl "${GRAVIS}.install-jdk-travis.sh" --output .install-jdk-travis.sh - # Get maven 3.1.1 - - "wget http://apache.mirror.iphh.net/maven/maven-3/3.1.1/binaries/apache-maven-3.1.1-bin.tar.gz && tar xfz apache-maven-3.1.1-bin.tar.gz && sudo mv apache-maven-3.1.1 /usr/local/maven-3.1.1 && sudo rm -f /usr/local/maven && sudo ln -s /usr/local/maven-3.1.1 /usr/local/maven" - -before_script: - - bash .install-jdk-travis.sh - -script: - - /usr/local/maven/bin/mvn verify -B diff --git a/CHANGES b/CHANGES index 44bc0194a5..53059ccafd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,12 +1,14 @@ jsoup changelog *** Release 1.14.1 [PENDING] + * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). + + * Build Improvement: moved to GitHub Workflows for build verification. + * Change: updated the minimum supported Java version from Java 7 to Java 8. * Change: updated the minimum Android API level from 8 to 10. - * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). - *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/README.md b/README.md index 7dada4fb16..bc24827709 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ jsoup is designed to deal with all varieties of HTML found in the wild; from pri See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API documentation](https://jsoup.org/apidocs/). -[![Build Status](https://travis-ci.org/jhy/jsoup.svg?branch=master)](https://travis-ci.org/jhy/jsoup) +[![Build Status](https://github.com/jhy/jsoup/workflows/Build/badge.svg)](https://github.com/jhy/jsoup/actions?query=workflow%3ABuild) ## Example Fetch the [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html): @@ -31,7 +31,7 @@ for (Element headline : newsHeadlines) { [Online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g), [full source](https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/examples/Wikipedia.java). ## Open source -jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available at [GitHub](https://github.com/jhy/jsoup/tree/master/src/main/java/org/jsoup). +jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available at [GitHub](https://github.com/jhy/jsoup). ## Getting started 1. [Download](https://jsoup.org/download) the latest jsoup jar (or add it to your Maven/Gradle build) From 6c689fce6aae9410b01bb47c12679662ba3df5d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Dec 2020 16:02:39 +1100 Subject: [PATCH 439/774] Bump jetty-server from 9.4.26.v20200117 to 9.4.35.v20201120 (#1457) Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.26.v20200117 to 9.4.35.v20201120. - [Release notes](https://github.com/eclipse/jetty.project/releases) - [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.26.v20200117...jetty-9.4.35.v20201120) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3b6fdb595e..b9e08a07b0 100644 --- a/pom.xml +++ b/pom.xml @@ -276,7 +276,7 @@ <!-- jetty for webserver integration tests. 9.2 is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.26.v20200117</version> + <version>9.4.35.v20201120</version> <scope>test</scope> </dependency> From c52f8312c2838918e27865370a4a7fef1bd986ea Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 10 Dec 2020 16:31:27 +1100 Subject: [PATCH 440/774] Fix Jetty version Both Jetty-Server and -Servlet jars must be on same version. --- CHANGES | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 53059ccafd..62e2640bbc 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,8 @@ jsoup changelog * Change: updated the minimum Android API level from 8 to 10. + * Change: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/pom.xml b/pom.xml index b9e08a07b0..8ae9275608 100644 --- a/pom.xml +++ b/pom.xml @@ -285,7 +285,7 @@ <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.26.v20200117</version> + <version>9.4.35.v20201120</version> <scope>test</scope> </dependency> From 22405b7980ec56d4570c24eb1cda612d5f6775dd Mon Sep 17 00:00:00 2001 From: Hannes Erven <hannes@handig-eekhoorn.at> Date: Thu, 10 Dec 2020 06:41:08 +0100 Subject: [PATCH 441/774] Limit reading CharBuffer.array() to CharBuffer.limit() to avoid additional NULL characters at the end of some inputs (#1452) --- src/main/java/org/jsoup/helper/DataUtil.java | 2 +- src/test/java/org/jsoup/helper/DataUtilTest.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index c4d94bbe6a..d0385e3f5d 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -130,7 +130,7 @@ static Document parseInputStream(InputStream input, String charsetName, String b try { CharBuffer defaultDecoded = Charset.forName(defaultCharset).decode(firstBytes); if (defaultDecoded.hasArray()) - doc = parser.parseInput(new CharArrayReader(defaultDecoded.array()), baseUri); + doc = parser.parseInput(new CharArrayReader(defaultDecoded.array(), defaultDecoded.arrayOffset(), defaultDecoded.limit()), baseUri); else doc = parser.parseInput(defaultDecoded.toString(), baseUri); } catch (UncheckedIOException e) { diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index f585bb26c4..a775723fa0 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -161,6 +161,14 @@ public void supportsUTF8BOM() throws IOException { assertEquals("OK", doc.head().select("title").text()); } + @Test + public void noExtraNULLBytes() throws IOException { + final byte[] b = "<html><head><meta charset=\"UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div><u>ü</u>ü</div></body></html>".getBytes("UTF-8"); + + Document doc = Jsoup.parse(new ByteArrayInputStream(b), null, ""); + assertFalse( doc.outerHtml().contains("\u0000") ); + } + @Test public void supportsZippedUTF8BOM() throws IOException { File in = getFile("/bomtests/bom_utf8.html.gz"); From 54a3740dc26eeecbbac4770a977431cc91162e18 Mon Sep 17 00:00:00 2001 From: Hannes Erven <hannes@handig-eekhoorn.at> Date: Thu, 10 Dec 2020 06:43:34 +0100 Subject: [PATCH 442/774] Add bin/ to .gitignore (#1439) (Eclipse's default output folder) --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1200b560ab..c696abb04e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ target/ .project .settings/ *Thrash* - +bin/ From 6ea3c3676bc7f53636dd1db0b5e6937282473798 Mon Sep 17 00:00:00 2001 From: Jiri Pejchal <jiri.pejchal@gmail.com> Date: Fri, 11 Dec 2020 01:12:54 +0100 Subject: [PATCH 443/774] Use constants for the standard charsets (#1455) Also solves thread contention in the constructor of OutputSettings. Fixes #1454 --- src/main/java/org/jsoup/helper/HttpConnection.java | 13 ++++--------- src/main/java/org/jsoup/nodes/Document.java | 3 ++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 73cb7d4b36..2db6a771e1 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -19,7 +19,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.MalformedURLException; @@ -32,6 +31,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -386,15 +386,10 @@ public List<String> headers(String name) { } private static String fixHeaderEncoding(String val) { - try { - byte[] bytes = val.getBytes("ISO-8859-1"); - if (!looksLikeUtf8(bytes)) - return val; - return new String(bytes, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // shouldn't happen as these both always exist + byte[] bytes = val.getBytes(StandardCharsets.ISO_8859_1); + if (!looksLikeUtf8(bytes)) return val; - } + return new String(bytes, StandardCharsets.UTF_8); } private static boolean looksLikeUtf8(byte[] input) { diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 92ae0b72ed..449baba4dd 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -9,6 +9,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -398,7 +399,7 @@ public enum Syntax {html, xml} private Syntax syntax = Syntax.html; public OutputSettings() { - charset(Charset.forName("UTF8")); + charset(StandardCharsets.UTF_8); } /** From 6e24d79eb0726ef97aa7e579195a650569894fe0 Mon Sep 17 00:00:00 2001 From: Yohei Kishimoto <morokosi@users.noreply.github.com> Date: Fri, 11 Dec 2020 09:19:55 +0900 Subject: [PATCH 444/774] Improve StringBuilder cache performance under high concurrency (#1402) --- .../java/org/jsoup/internal/StringUtil.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index bc1d5887ca..b313503155 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -229,7 +229,12 @@ public static String resolve(final String baseUrl, final String relUrl) { } } - private static final Stack<StringBuilder> builders = new Stack<>(); + private static final ThreadLocal<Stack<StringBuilder>> threadLocalBuilders = new ThreadLocal<Stack<StringBuilder>>() { + @Override + protected Stack<StringBuilder> initialValue() { + return new Stack<>(); + } + }; /** * Maintains cached StringBuilders in a flyweight pattern, to minimize new StringBuilder GCs. The StringBuilder is @@ -239,11 +244,10 @@ public static String resolve(final String baseUrl, final String relUrl) { * @return an empty StringBuilder */ public static StringBuilder borrowBuilder() { - synchronized (builders) { - return builders.empty() ? - new StringBuilder(MaxCachedBuilderSize) : - builders.pop(); - } + Stack<StringBuilder> builders = threadLocalBuilders.get(); + return builders.empty() ? + new StringBuilder(MaxCachedBuilderSize) : + builders.pop(); } /** @@ -261,12 +265,11 @@ public static String releaseBuilder(StringBuilder sb) { else sb.delete(0, sb.length()); // make sure it's emptied on release - synchronized (builders) { - builders.push(sb); + Stack<StringBuilder> builders = threadLocalBuilders.get(); + builders.push(sb); - while (builders.size() > MaxIdleBuilders) { - builders.pop(); - } + while (builders.size() > MaxIdleBuilders) { + builders.pop(); } return string; } From aa36135f58ada8c5198de0a541009357aa21db4e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 11:27:19 +1100 Subject: [PATCH 445/774] Changelog notes for PRs --- CHANGES | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 62e2640bbc..1997b26b2c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,15 +1,21 @@ jsoup changelog *** Release 1.14.1 [PENDING] + * Change: updated the minimum supported Java version from Java 7 to Java 8. + + * Change: updated the minimum Android API level from 8 to 10. + * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). - * Build Improvement: moved to GitHub Workflows for build verification. + * Improvement: reduced thread content in HttpConnection and Document. + <https://github.com/jhy/jsoup/pull/1455> - * Change: updated the minimum supported Java version from Java 7 to Java 8. + * Improvement: better parsing performance when under high thread concurrency + <https://github.com/jhy/jsoup/pull/1402> - * Change: updated the minimum Android API level from 8 to 10. + * Build Improvement: moved to GitHub Workflows for build verification. - * Change: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. + * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the From c0b275361ce8409f85de3573d89f6a617f0084d5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 11:50:49 +1100 Subject: [PATCH 446/774] Reduce hits to Charset.forName, prefer static Charset --- src/main/java/org/jsoup/helper/DataUtil.java | 14 ++++++++------ src/main/java/org/jsoup/helper/HttpConnection.java | 13 +++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index d0385e3f5d..4abe3ef580 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -25,6 +25,7 @@ import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Random; import java.util.regex.Matcher; @@ -37,7 +38,8 @@ */ public final class DataUtil { private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:[\"'])?([^\\s,;\"']*)"); - static final String defaultCharset = "UTF-8"; // used if not found in header or meta charset + static final Charset defaultCharset = StandardCharsets.UTF_8; + static final String defaultCharsetName = defaultCharset.name(); // used if not found in header or meta charset private static final int firstReadBufferSize = 1024 * 5; static final int bufferSize = 1024 * 32; private static final char[] mimeBoundaryChars = @@ -128,7 +130,7 @@ static Document parseInputStream(InputStream input, String charsetName, String b if (charsetName == null) { // determine from meta. safe first parse as UTF-8 try { - CharBuffer defaultDecoded = Charset.forName(defaultCharset).decode(firstBytes); + CharBuffer defaultDecoded = defaultCharset.decode(firstBytes); if (defaultDecoded.hasArray()) doc = parser.parseInput(new CharArrayReader(defaultDecoded.array(), defaultDecoded.arrayOffset(), defaultDecoded.limit()), baseUri); else @@ -166,7 +168,7 @@ else if (first instanceof Comment) { } } foundCharset = validateCharset(foundCharset); - if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharset)) { // need to re-decode. (case insensitive check here to match how validate works) + if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharsetName)) { // need to re-decode. (case insensitive check here to match how validate works) foundCharset = foundCharset.trim().replaceAll("[\"']", ""); charsetName = foundCharset; doc = null; @@ -178,7 +180,7 @@ else if (first instanceof Comment) { } if (doc == null) { if (charsetName == null) - charsetName = defaultCharset; + charsetName = defaultCharsetName; BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); if (bomCharset != null && bomCharset.offset) { // creating the buffered reader ignores the input pos, so must skip here long skipped = reader.skip(1); @@ -190,11 +192,11 @@ else if (first instanceof Comment) { // io exception when parsing (not seen before because reading the stream as we go) throw e.ioException(); } - Charset charset = Charset.forName(charsetName); + Charset charset = charsetName.equals(defaultCharsetName) ? defaultCharset : Charset.forName(charsetName); doc.outputSettings().charset(charset); if (!charset.canEncode()) { // some charsets can read but not encode; switch to an encodable charset and update the meta el - doc.charset(Charset.forName(defaultCharset)); + doc.charset(defaultCharset); } } input.close(); diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 2db6a771e1..7452f17259 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -541,7 +541,7 @@ public static class Request extends HttpConnection.Base<Connection.Request> impl private boolean ignoreContentType = false; private Parser parser; private boolean parserDefined = false; // called parser(...) vs initialized in ctor - private String postDataCharset = DataUtil.defaultCharset; + private String postDataCharset = DataUtil.defaultCharsetName; private SSLSocketFactory sslSocketFactory; Request() { @@ -852,11 +852,8 @@ private void prepareByteData() { public String body() { prepareByteData(); // charset gets set from header on execute, and from meta-equiv on parse. parse may not have happened yet - String body; - if (charset == null) - body = Charset.forName(DataUtil.defaultCharset).decode(byteData).toString(); - else - body = Charset.forName(charset).decode(byteData).toString(); + String body = (charset == null ? DataUtil.defaultCharset : Charset.forName(charset)) + .decode(byteData).toString(); ((Buffer)byteData).rewind(); // cast to avoid covariant return type change in jdk9 return body; } @@ -1111,9 +1108,9 @@ private static void serialiseRequestUrl(Connection.Request req) throws IOExcepti else first = false; url - .append(URLEncoder.encode(keyVal.key(), DataUtil.defaultCharset)) + .append(URLEncoder.encode(keyVal.key(), DataUtil.defaultCharsetName)) .append('=') - .append(URLEncoder.encode(keyVal.value(), DataUtil.defaultCharset)); + .append(URLEncoder.encode(keyVal.value(), DataUtil.defaultCharsetName)); } req.url(new URL(StringUtil.releaseBuilder(url))); req.data().clear(); // moved into url as get params From 28a4591949d5cb7d6c59a1dcfa5ee83048ad139a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 12:03:38 +1100 Subject: [PATCH 447/774] Remove StandardCharset use As not in Android 10 which is a runtime target. --- src/main/java/org/jsoup/helper/DataUtil.java | 11 +++++------ src/main/java/org/jsoup/helper/HttpConnection.java | 2 +- src/main/java/org/jsoup/nodes/Document.java | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 4abe3ef580..332cccff21 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -25,7 +25,6 @@ import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; -import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Random; import java.util.regex.Matcher; @@ -38,8 +37,8 @@ */ public final class DataUtil { private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:[\"'])?([^\\s,;\"']*)"); - static final Charset defaultCharset = StandardCharsets.UTF_8; - static final String defaultCharsetName = defaultCharset.name(); // used if not found in header or meta charset + public static final Charset UTF_8 = Charset.forName("UTF-8"); // Don't use StandardCharsets, as those only appear in Android API 19, and we target 10. + static final String defaultCharsetName = UTF_8.name(); // used if not found in header or meta charset private static final int firstReadBufferSize = 1024 * 5; static final int bufferSize = 1024 * 32; private static final char[] mimeBoundaryChars = @@ -130,7 +129,7 @@ static Document parseInputStream(InputStream input, String charsetName, String b if (charsetName == null) { // determine from meta. safe first parse as UTF-8 try { - CharBuffer defaultDecoded = defaultCharset.decode(firstBytes); + CharBuffer defaultDecoded = UTF_8.decode(firstBytes); if (defaultDecoded.hasArray()) doc = parser.parseInput(new CharArrayReader(defaultDecoded.array(), defaultDecoded.arrayOffset(), defaultDecoded.limit()), baseUri); else @@ -192,11 +191,11 @@ else if (first instanceof Comment) { // io exception when parsing (not seen before because reading the stream as we go) throw e.ioException(); } - Charset charset = charsetName.equals(defaultCharsetName) ? defaultCharset : Charset.forName(charsetName); + Charset charset = charsetName.equals(defaultCharsetName) ? UTF_8 : Charset.forName(charsetName); doc.outputSettings().charset(charset); if (!charset.canEncode()) { // some charsets can read but not encode; switch to an encodable charset and update the meta el - doc.charset(defaultCharset); + doc.charset(UTF_8); } } input.close(); diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 7452f17259..13ca5c0523 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -852,7 +852,7 @@ private void prepareByteData() { public String body() { prepareByteData(); // charset gets set from header on execute, and from meta-equiv on parse. parse may not have happened yet - String body = (charset == null ? DataUtil.defaultCharset : Charset.forName(charset)) + String body = (charset == null ? DataUtil.UTF_8 : Charset.forName(charset)) .decode(byteData).toString(); ((Buffer)byteData).rewind(); // cast to avoid covariant return type change in jdk9 return body; diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 449baba4dd..c39a75087e 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -1,5 +1,6 @@ package org.jsoup.nodes; +import org.jsoup.helper.DataUtil; import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; @@ -9,7 +10,6 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -399,7 +399,7 @@ public enum Syntax {html, xml} private Syntax syntax = Syntax.html; public OutputSettings() { - charset(StandardCharsets.UTF_8); + charset(DataUtil.UTF_8); } /** From ee53143738f61fe9e85daf4fa5e4ebe00830caf0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 12:11:36 +1100 Subject: [PATCH 448/774] Remove other StandardCharset use And a personal reminder to use mvn verify before commit! Not just test, to make sure the Android sniffer runs. --- src/main/java/org/jsoup/helper/HttpConnection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 13ca5c0523..ff40431daf 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -64,6 +64,8 @@ public class HttpConnection implements Connection { public static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; private static final int HTTP_TEMP_REDIR = 307; // http/1.1 temporary redirect, not in Java's set. private static final String DefaultUploadType = "application/octet-stream"; + private static final Charset UTF_8 = Charset.forName("UTF-8"); // Don't use StandardCharsets, not in Android API 10. + private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); public static Connection connect(String url) { Connection con = new HttpConnection(); @@ -386,10 +388,10 @@ public List<String> headers(String name) { } private static String fixHeaderEncoding(String val) { - byte[] bytes = val.getBytes(StandardCharsets.ISO_8859_1); + byte[] bytes = val.getBytes(ISO_8859_1); if (!looksLikeUtf8(bytes)) return val; - return new String(bytes, StandardCharsets.UTF_8); + return new String(bytes, UTF_8); } private static boolean looksLikeUtf8(byte[] input) { From 62f5035bc6b466f59231c59a7ed414db8783c0c3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 13:54:23 +1100 Subject: [PATCH 449/774] GitHub Actions tweaks Run on pull request, minimize Java versions, cache mvn artifacts --- .github/workflows/build.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 18394b44ea..ef49f13faf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,5 @@ name: Build -on: [push] +on: [push, pull_request] jobs: test: @@ -7,17 +7,28 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - java: [8, 9, 11, 12, 13, 15, 16-ea] + # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time + java: [8, 11, 15, 16-ea] fail-fast: false max-parallel: 4 name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Verify with Maven run: mvn -X verify -B --file pom.xml ... From 2ccd3f886857d2189254aaa198c8257dacd6964c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 14:06:56 +1100 Subject: [PATCH 450/774] Githhub Build Fix cache write contention (with java version key) --- .github/workflows/build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef49f13faf..f4f06da63b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,6 @@ jobs: # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time java: [8, 11, 15, 16-ea] fail-fast: false - max-parallel: 4 name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: - name: Checkout @@ -25,10 +24,13 @@ jobs: uses: actions/cache@v2 with: path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + key: ${{ runner.os }}-maven-java-${{ matrix.java }}-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - - name: Verify with Maven + + - name: Maven Compile + run: mvn -X compile -B --file pom.xml + + - name: Maven Verify run: mvn -X verify -B --file pom.xml ... From 6e3c98c4a578fb7910241e72a5a33014bb8e85f1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 15:52:53 +1100 Subject: [PATCH 451/774] Fix null guards on start tag attributes Fixes #1404 --- CHANGES | 3 ++ .../org/jsoup/parser/HtmlTreeBuilder.java | 2 +- .../jsoup/parser/HtmlTreeBuilderState.java | 30 +++++++++++-------- src/main/java/org/jsoup/parser/Token.java | 16 +++++----- src/main/java/org/jsoup/parser/Tokeniser.java | 2 +- .../java/org/jsoup/parser/XmlTreeBuilder.java | 2 +- .../java/org/jsoup/parser/HtmlParserTest.java | 16 ++++++++++ 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 1997b26b2c..3d3f1e8c84 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ jsoup changelog * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. + * Bugfix: when parsing HTML, could throw NPEs on some tags (isindex or table>input). + <https://github.com/jhy/jsoup/issues/1404> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index b879de3eb5..39520ed5f7 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -196,7 +196,7 @@ void error(HtmlTreeBuilderState state) { Element insert(final Token.StartTag startTag) { // cleanup duplicate attributes: - if (startTag.attributes != null && !startTag.attributes.isEmpty()) { + if (startTag.hasAttributes() && !startTag.attributes.isEmpty()) { int dupes = startTag.attributes.deduplicate(settings); if (dupes > 0) { error("Duplicate attribute"); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 21c8f64c23..0d660d197b 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -339,9 +339,11 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.error(this); // merge attributes onto real html Element html = tb.getStack().get(0); - for (Attribute attribute : startTag.getAttributes()) { - if (!html.hasAttr(attribute.getKey())) - html.attributes().put(attribute); + if (startTag.hasAttributes()) { + for (Attribute attribute : startTag.attributes) { + if (!html.hasAttr(attribute.getKey())) + html.attributes().put(attribute); + } } break; case "body": @@ -353,9 +355,11 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { } else { tb.framesetOk(false); Element body = stack.get(1); - for (Attribute attribute : startTag.getAttributes()) { - if (!body.hasAttr(attribute.getKey())) - body.attributes().put(attribute); + if (startTag.hasAttributes()) { + for (Attribute attribute : startTag.attributes) { + if (!body.hasAttr(attribute.getKey())) + body.attributes().put(attribute); + } } } break; @@ -451,14 +455,14 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { return false; tb.processStartTag("form"); - if (startTag.attributes.hasKey("action")) { + if (startTag.hasAttribute("action")) { Element form = tb.getFormElement(); form.attr("action", startTag.attributes.get("action")); } tb.processStartTag("hr"); tb.processStartTag("label"); // hope you like english. - String prompt = startTag.attributes.hasKey("prompt") ? + String prompt = startTag.hasAttribute("prompt") ? startTag.attributes.get("prompt") : "This is a searchable index. Enter search keywords: "; @@ -466,9 +470,11 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { // input Attributes inputAttribs = new Attributes(); - for (Attribute attr : startTag.attributes) { - if (!inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) - inputAttribs.put(attr); + if (startTag.hasAttributes()) { + for (Attribute attr : startTag.attributes) { + if (!inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) + inputAttribs.put(attr); + } } inputAttribs.put("name", "isindex"); tb.processStartTag("input", inputAttribs); @@ -931,7 +937,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (inSorted(name, InTableToHead)) { return tb.process(t, InHead); } else if (name.equals("input")) { - if (!startTag.attributes.get("type").equalsIgnoreCase("hidden")) { + if (!(startTag.hasAttributes() && startTag.attributes.get("type").equalsIgnoreCase("hidden"))) { return anythingElse(t, tb); } else { tb.insertEmpty(startTag); diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index f9061377fe..56f72b5885 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -123,6 +123,14 @@ else if (hasEmptyAttributeValue) pendingAttributeValueS = null; } + final boolean hasAttributes() { + return attributes != null; + } + + final boolean hasAttribute(String key) { + return attributes != null && attributes.hasKey(key); + } + final void finaliseTag() { // finalises for emit if (pendingAttributeName != null) { @@ -151,12 +159,6 @@ final boolean isSelfClosing() { return selfClosing; } - final Attributes getAttributes() { - if (attributes == null) - attributes = new Attributes(); - return attributes; - } - // these appenders are rarely hit in not null state-- caused by null chars. final void appendTagName(String append) { tagName = tagName == null ? append : tagName.concat(append); @@ -237,7 +239,7 @@ StartTag nameAttr(String name, Attributes attributes) { @Override public String toString() { - if (attributes != null && attributes.size() > 0) + if (hasAttributes() && attributes.size() > 0) return "<" + name() + " " + attributes.toString() + ">"; else return "<" + name() + ">"; diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 6d2e844561..db053ea8f2 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -85,7 +85,7 @@ void emit(Token token) { lastStartTag = startTag.tagName; } else if (token.type == Token.TokenType.EndTag) { Token.EndTag endTag = (Token.EndTag) token; - if (endTag.attributes != null) + if (endTag.hasAttributes()) error("Attributes incorrectly present on end tag"); } } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 361725119f..d68156dc38 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -75,7 +75,7 @@ private void insertNode(Node node) { Element insert(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name(), settings); // todo: wonder if for xml parsing, should treat all tags as unknown? because it's not html. - if (startTag.attributes != null) + if (startTag.hasAttributes()) startTag.attributes.deduplicate(settings); Element el = new Element(tag, null, settings.normalizeAttributes(startTag.attributes)); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 000ff97888..8eb27cb387 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -434,6 +434,22 @@ public class HtmlParserTest { assertEquals("http://example.com/foo", doc.select("a").first().absUrl("href")); } + @Test public void parseBodyIsIndexNoAttributes() { + // https://github.com/jhy/jsoup/issues/1404 + String expectedHtml = "<form>\n" + + " <hr><label>This is a searchable index. Enter search keywords: <input name=\"isindex\"></label>\n" + + " <hr>\n" + + "</form>"; + Document doc = Jsoup.parse("<isindex>"); + assertEquals(expectedHtml, doc.body().html()); + + doc = Jsoup.parseBodyFragment("<isindex>"); + assertEquals(expectedHtml, doc.body().html()); + + doc = Jsoup.parseBodyFragment("<table><input></table>"); + assertEquals("<input>\n<table></table>", doc.body().html()); + } + @Test public void handlesUnknownNamespaceTags() { // note that the first foo:bar should not really be allowed to be self closing, if parsed in html mode. String h = "<foo:bar id='1' /><abc:def id=2>Foo<p>Hello</p></abc:def><foo:bar>There</foo:bar>"; From f6388656f6c1ce111bc2dd158bf5a3732bda0605 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 11 Dec 2020 16:12:56 +1100 Subject: [PATCH 452/774] Fixed javadoc and added testcase for Element#isBlock Fixes #1342 --- src/main/java/org/jsoup/nodes/Element.java | 2 +- src/test/java/org/jsoup/nodes/ElementTest.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 583f7d1a04..8f763cbc89 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -176,7 +176,7 @@ public Tag tag() { /** * Test if this element is a block-level element. (E.g. {@code <div> == true} or an inline element - * {@code <p> == false}). + * {@code <span> == false}). * * @return true if block, false if not (and thus inline) */ diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index b22e66ecb4..6cc67e8c12 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1597,4 +1597,12 @@ public void testChildSizeWithMixedContent() { assertEquals(2, row.childrenSize()); assertEquals(5, row.childNodeSize()); } + + @Test public void isBlock() { + String html = "<div><p><span>Hello</span>"; + Document doc = Jsoup.parse(html); + assertTrue(doc.selectFirst("div").isBlock()); + assertTrue(doc.selectFirst("p").isBlock()); + assertFalse(doc.selectFirst("span").isBlock()); + } } From 27b656744b273eca3f43111fe20cc15aa978e52d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 15:12:56 +1100 Subject: [PATCH 453/774] Build - report on API compat between verisons, and store build artifacts --- .github/workflows/build.yml | 9 +++++++++ pom.xml | 21 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4f06da63b..20c61f2c28 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,4 +33,13 @@ jobs: - name: Maven Verify run: mvn -X verify -B --file pom.xml + + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: jsoup-artifacts + path: | + target/ + !target/classes + !target/test-classes ... diff --git a/pom.xml b/pom.xml index 8ae9275608..d49e6c9090 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ </configuration> </plugin> <plugin> - <!-- this plugin allows us to ensure Java 8 API compatibility --> + <!-- Ensure Java 8 and Android 10 API compatibility --> <groupId>org.codehaus.mojo</groupId> <artifactId>animal-sniffer-maven-plugin</artifactId> <version>1.16</version> @@ -178,6 +178,25 @@ <threadCount>8</threadCount> </configuration> </plugin> + <plugin> + <!-- API version compat check - https://siom79.github.io/japicmp/ --> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>0.14.4</version> + <configuration> + <parameter> + <!-- Just running the reports for now. jsoup policy is ok to remove deprecated methods on minor but not builds. --> + </parameter> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> <resources> <resource> From a4edbe03d1eee65f19c8727b1356763523438d09 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 15:32:17 +1100 Subject: [PATCH 454/774] Removing artifact retention; uploads were flaky On first build, 3 of 12 matrix builds failed with 500s to GitHub when uploading. Doesn't appear to have an option to not break build on upload failure, so will need to remove for now. --- .github/workflows/build.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20c61f2c28..f4f06da63b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,13 +33,4 @@ jobs: - name: Maven Verify run: mvn -X verify -B --file pom.xml - - - name: Upload Artifacts - uses: actions/upload-artifact@v2 - with: - name: jsoup-artifacts - path: | - target/ - !target/classes - !target/test-classes ... From 02bb8a90cad36fe00cf5887a2924df658b084ff5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 19:14:18 +1100 Subject: [PATCH 455/774] Corrected uses of Whitelist to Safelist (#1464) And introduced a (deprecated) compatibility shim. Tested with japicmp binary and source compatibility report, and checked binary compat with some test utilities (local testing). Many thanks to @drei01 (#1408), @nkiesel (#1423) and others for highlighting the issue and for PRs which this commit refines. --- CHANGES | 26 +- README.md | 2 +- pom.xml | 4 +- src/main/java/org/jsoup/Jsoup.java | 77 +- src/main/java/org/jsoup/safety/Cleaner.java | 42 +- src/main/java/org/jsoup/safety/Safelist.java | 655 ++++++++++++++++++ src/main/java/org/jsoup/safety/Whitelist.java | 626 ++--------------- .../java/org/jsoup/safety/package-info.java | 2 +- src/main/javadoc/overview.html | 24 +- .../java/org/jsoup/parser/HtmlParserTest.java | 14 +- .../java/org/jsoup/safety/CleanerTest.java | 122 ++-- .../org/jsoup/safety/CompatibilityTests.java | 99 +++ 12 files changed, 986 insertions(+), 707 deletions(-) create mode 100644 src/main/java/org/jsoup/safety/Safelist.java create mode 100644 src/test/java/org/jsoup/safety/CompatibilityTests.java diff --git a/CHANGES b/CHANGES index 3d3f1e8c84..76e2d8905d 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,10 @@ jsoup changelog * Change: updated the minimum Android API level from 8 to 10. + * Improvement: renamed the Whitelist class to Safelist, with the goal of more inclusive language. A shim is provided + for backwards compatibility (source and binary). This shim is marked as deprecated and will be removed in the + jsoup 1.15.1 release. + * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). * Improvement: reduced thread content in HttpConnection and Document. @@ -458,12 +462,12 @@ jsoup changelog * Added the new selector :containsData(), to find elements that hold data, like script and style tags. * Changed Jsoup.isValid(bodyHtml) to validate that the input contains only body HTML that is safe according to the - whitelist, and does not include HTML errors. And in the Jsoup.Cleaner.isValid(Document) method, make sure the doc + safelist, and does not include HTML errors. And in the Jsoup.Cleaner.isValid(Document) method, make sure the doc only includes body HTML. <https://github.com/jhy/jsoup/issues/245> <https://github.com/jhy/jsoup/issues/632> - * In Whitelists, validate that a removed protocol exists before removing said protocol. + * In Safelists, validate that a removed protocol exists before removing said protocol. * Allow the Jsoup.Connect thread to be interrupted when reading the input stream; helps when reading from a long stream of data that doesn't read timeout. @@ -658,10 +662,10 @@ jsoup changelog or your JDK doesn't support SNI. <https://github.com/jhy/jsoup/pull/343> - * Added ability to further tweak the canned Cleaner Whitelists by removing existing settings. + * Added ability to further tweak the canned Cleaner Safelists by removing existing settings. <https://github.com/jhy/jsoup/pull/449> - * Added option in Cleaner Whitelist to allow linking to in-page anchors (#) + * Added option in Cleaner Safelist to allow linking to in-page anchors (#) <https://github.com/jhy/jsoup/pull/441> * Use a lowercase doctype tag for HTML5 documents. @@ -734,7 +738,7 @@ jsoup changelog * If pretty-print is disabled, don't trim outer whitespace in Element.html() <https://github.com/jhy/jsoup/issues/368> - * In the HTML Cleaner, allow span tags in the basic whitelist, and span and div tags in the relaxed whitelist. + * In the HTML Cleaner, allow span tags in the basic safelist, and span and div tags in the relaxed safelist. * Added Element.cssSelector(), which returns a unique CSS selector/path for an element. <https://github.com/jhy/jsoup/pull/459> @@ -769,7 +773,7 @@ jsoup changelog * Added support for 'application/*+xml' mimetypes. <https://github.com/jhy/jsoup/pull/444> - * Fixed support for allowing script tags in cleaner whitelists. + * Fixed support for allowing script tags in cleaner Safelists. <https://github.com/jhy/jsoup/issues/299> <https://github.com/jhy/jsoup/issues/388> @@ -833,7 +837,7 @@ jsoup changelog * Introduced Parser.parseXmlFragment(), to allow easy parsing of XML fragments. <https://github.com/jhy/jsoup/issues/279> - * Allow Whitelist test methods to be extended + * Allow Safelist test methods to be extended <https://github.com/jhy/jsoup/issues/85> * Added Document.OutputSettings.outline mode, to aid HTML debugging by printing out in outline mode, similar to @@ -925,13 +929,13 @@ jsoup changelog * Fixed NPE when HTML fragment parsing a <style> tag <https://github.com/jhy/jsoup/issues/189> - * Fixed issue with :all pseudo-tag in HTML sanitizer when cleaning tags previously defined in whitelist + * Fixed issue with :all pseudo-tag in HTML sanitizer when cleaning tags previously defined in safelist <https://github.com/jhy/jsoup/issues/156> * Fixed NPE in Parser.parseFragment() when context parameter is null. <https://github.com/jhy/jsoup/issues/195> - * In HTML whitelists, when defining allowed attributes for a tag, automatically add the tag to the allowed list. + * In HTML Safelists, when defining allowed attributes for a tag, automatically add the tag to the allowed list. *** Release 1.6.2 [2012-Mar-27] * Added a simplified XML parsing mode, which can usefully parse valid and invalid XML, but does not enforce any HTML @@ -950,7 +954,7 @@ jsoup changelog * Updated jsoup.connect so that when requests made as POSTs are redirected, the redirect is followed as a GET. <https://github.com/jhy/jsoup/issues/120> - * Updated the Cleaner and whitelists to optionally preserve related links in elements, instead of converting them + * Updated the Cleaner and Safelists to optionally preserve related links in elements, instead of converting them to absolute links. * Updated the Cleaner to support custom allowed protocols such as "cid:" and "data:". @@ -1255,7 +1259,7 @@ jsoup changelog prepend(String), append(String); bulk methods for corresponding methods in Element. - * New feature: Jsoup.isValid(html, whitelist) method for user input + * New feature: Jsoup.isValid(html, safelist) method for user input form validation. * Improved Elements.attr(String) to find first matching element diff --git a/README.md b/README.md index bc24827709..76d88556ab 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ * scrape and [parse](https://jsoup.org/cookbook/input/parse-document-from-string) HTML from a URL, file, or string * find and [extract data](https://jsoup.org/cookbook/extracting-data/selector-syntax), using DOM traversal or CSS selectors * manipulate the [HTML elements](https://jsoup.org/cookbook/modifying-data/set-html), attributes, and text -* [clean](https://jsoup.org/cookbook/cleaning-html/whitelist-sanitizer) user-submitted content against a safe white-list, to prevent XSS attacks +* [clean](https://jsoup.org/cookbook/cleaning-html/safelist-sanitizer) user-submitted content against a safe-list, to prevent XSS attacks * output tidy HTML jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; jsoup will create a sensible parse tree. diff --git a/pom.xml b/pom.xml index d49e6c9090..d0d0000cb1 100644 --- a/pom.xml +++ b/pom.xml @@ -185,7 +185,9 @@ <version>0.14.4</version> <configuration> <parameter> - <!-- Just running the reports for now. jsoup policy is ok to remove deprecated methods on minor but not builds. --> + <!-- jsoup policy is ok to remove deprecated methods on minor but not builds. will need to temp remove on bump to 1.15.1 and manually validate --> + <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> + <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> </parameter> </configuration> <executions> diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 84a5e34efd..5fe628614f 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -1,11 +1,12 @@ package org.jsoup; +import org.jsoup.helper.DataUtil; +import org.jsoup.helper.HttpConnection; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.jsoup.safety.Cleaner; +import org.jsoup.safety.Safelist; import org.jsoup.safety.Whitelist; -import org.jsoup.helper.DataUtil; -import org.jsoup.helper.HttpConnection; import java.io.File; import java.io.IOException; @@ -184,70 +185,106 @@ public static Document parse(URL url, int timeoutMillis) throws IOException { } /** - Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of permitted + Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through an allow-list of safe tags and attributes. @param bodyHtml input untrusted HTML (body fragment) @param baseUri URL to resolve relative URLs against - @param whitelist white-list of permitted HTML elements + @param safelist list of permitted HTML elements @return safe HTML (body fragment) @see Cleaner#clean(Document) */ - public static String clean(String bodyHtml, String baseUri, Whitelist whitelist) { + public static String clean(String bodyHtml, String baseUri, Safelist safelist) { Document dirty = parseBodyFragment(bodyHtml, baseUri); - Cleaner cleaner = new Cleaner(whitelist); + Cleaner cleaner = new Cleaner(safelist); Document clean = cleaner.clean(dirty); return clean.body().html(); } /** - Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of permitted + Use {@link #clean(String, String, Safelist)} instead. + @deprecated as of 1.14.1. + */ + @Deprecated + public static String clean(String bodyHtml, String baseUri, Whitelist safelist) { + return clean(bodyHtml, baseUri, (Safelist) safelist); + } + + /** + Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of permitted tags and attributes. @param bodyHtml input untrusted HTML (body fragment) - @param whitelist white-list of permitted HTML elements + @param safelist list of permitted HTML elements @return safe HTML (body fragment) @see Cleaner#clean(Document) */ - public static String clean(String bodyHtml, Whitelist whitelist) { - return clean(bodyHtml, "", whitelist); + public static String clean(String bodyHtml, Safelist safelist) { + return clean(bodyHtml, "", safelist); + } + + /** + Use {@link #clean(String, Safelist)} instead. + @deprecated as of 1.14.1. + */ + @Deprecated + public static String clean(String bodyHtml, Whitelist safelist) { + return clean(bodyHtml, (Safelist) safelist); } /** - * Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a white-list of + * Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of * permitted tags and attributes. * <p>The HTML is treated as a body fragment; it's expected the cleaned HTML will be used within the body of an * existing document. If you want to clean full documents, use {@link Cleaner#clean(Document)} instead, and add - * structural tags (<code>html, head, body</code> etc) to the whitelist. + * structural tags (<code>html, head, body</code> etc) to the safelist. * * @param bodyHtml input untrusted HTML (body fragment) * @param baseUri URL to resolve relative URLs against - * @param whitelist white-list of permitted HTML elements + * @param safelist list of permitted HTML elements * @param outputSettings document output settings; use to control pretty-printing and entity escape modes * @return safe HTML (body fragment) * @see Cleaner#clean(Document) */ - public static String clean(String bodyHtml, String baseUri, Whitelist whitelist, Document.OutputSettings outputSettings) { + public static String clean(String bodyHtml, String baseUri, Safelist safelist, Document.OutputSettings outputSettings) { Document dirty = parseBodyFragment(bodyHtml, baseUri); - Cleaner cleaner = new Cleaner(whitelist); + Cleaner cleaner = new Cleaner(safelist); Document clean = cleaner.clean(dirty); clean.outputSettings(outputSettings); return clean.body().html(); } /** - Test if the input body HTML has only tags and attributes allowed by the Whitelist. Useful for form validation. + Use {@link #clean(String, String, Safelist, Document.OutputSettings)} instead. + @deprecated as of 1.14.1. + */ + @Deprecated + public static String clean(String bodyHtml, String baseUri, Whitelist safelist, Document.OutputSettings outputSettings) { + return clean(bodyHtml, baseUri, (Safelist) safelist, outputSettings); + } + + /** + Test if the input body HTML has only tags and attributes allowed by the Safelist. Useful for form validation. <p>The input HTML should still be run through the cleaner to set up enforced attributes, and to tidy the output. <p>Assumes the HTML is a body fragment (i.e. will be used in an existing HTML document body.) @param bodyHtml HTML to test - @param whitelist whitelist to test against + @param safelist safelist to test against @return true if no tags or attributes were removed; false otherwise - @see #clean(String, org.jsoup.safety.Whitelist) + @see #clean(String, Safelist) + */ + public static boolean isValid(String bodyHtml, Safelist safelist) { + return new Cleaner(safelist).isValidBodyHtml(bodyHtml); + } + + /** + Use {@link #isValid(String, Safelist)} instead. + @deprecated as of 1.14.1. */ - public static boolean isValid(String bodyHtml, Whitelist whitelist) { - return new Cleaner(whitelist).isValidBodyHtml(bodyHtml); + @Deprecated + public static boolean isValid(String bodyHtml, Whitelist safelist) { + return isValid(bodyHtml, (Safelist) safelist); } } diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 22ecb0b332..9b6242ff65 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -18,34 +18,44 @@ /** - The whitelist based HTML cleaner. Use to ensure that end-user provided HTML contains only the elements and attributes + The safelist based HTML cleaner. Use to ensure that end-user provided HTML contains only the elements and attributes that you are expecting; no junk, and no cross-site scripting attacks! <p> - The HTML cleaner parses the input as HTML and then runs it through a white-list, so the output HTML can only contain - HTML that is allowed by the whitelist. + The HTML cleaner parses the input as HTML and then runs it through a safe-list, so the output HTML can only contain + HTML that is allowed by the safelist. </p> <p> It is assumed that the input HTML is a body fragment; the clean methods only pull from the source's body, and the - canned white-lists only allow body contained tags. + canned safe-lists only allow body contained tags. </p> <p> Rather than interacting directly with a Cleaner object, generally see the {@code clean} methods in {@link org.jsoup.Jsoup}. </p> */ public class Cleaner { - private Whitelist whitelist; + private Safelist safelist; /** - Create a new cleaner, that sanitizes documents using the supplied whitelist. - @param whitelist white-list to clean with + Create a new cleaner, that sanitizes documents using the supplied safelist. + @param safelist safe-list to clean with */ + public Cleaner(Safelist safelist) { + Validate.notNull(safelist); + this.safelist = safelist; + } + + /** + Use {@link #Cleaner(Safelist)} instead. + @deprecated as of 1.14.1. + */ + @Deprecated public Cleaner(Whitelist whitelist) { Validate.notNull(whitelist); - this.whitelist = whitelist; + new Cleaner((Safelist) whitelist); } /** - Creates a new, clean document, from the original dirty document, containing only elements allowed by the whitelist. + Creates a new, clean document, from the original dirty document, containing only elements allowed by the safelist. The original document is not modified. Only elements from the dirt document's <code>body</code> are used. @param dirtyDocument Untrusted base document to clean. @return cleaned document. @@ -61,8 +71,8 @@ public Document clean(Document dirtyDocument) { } /** - Determines if the input document <b>body</b>is valid, against the whitelist. It is considered valid if all the tags and attributes - in the input HTML are allowed by the whitelist, and that there is no content in the <code>head</code>. + Determines if the input document <b>body</b>is valid, against the safelist. It is considered valid if all the tags and attributes + in the input HTML are allowed by the safelist, and that there is no content in the <code>head</code>. <p> This method can be used as a validator for user input. An invalid document will still be cleaned successfully using the {@link #clean(Document)} document. If using as a validator, it is recommended to still clean the document @@ -107,7 +117,7 @@ public void head(Node source, int depth) { if (source instanceof Element) { Element sourceEl = (Element) source; - if (whitelist.isSafeTag(sourceEl.normalName())) { // safe, clone and copy safe attrs + if (safelist.isSafeTag(sourceEl.normalName())) { // safe, clone and copy safe attrs ElementMeta meta = createSafeElement(sourceEl); Element destChild = meta.el; destination.appendChild(destChild); @@ -121,7 +131,7 @@ public void head(Node source, int depth) { TextNode sourceText = (TextNode) source; TextNode destText = new TextNode(sourceText.getWholeText()); destination.appendChild(destText); - } else if (source instanceof DataNode && whitelist.isSafeTag(source.parent().nodeName())) { + } else if (source instanceof DataNode && safelist.isSafeTag(source.parent().nodeName())) { DataNode sourceData = (DataNode) source; DataNode destData = new DataNode(sourceData.getWholeData()); destination.appendChild(destData); @@ -131,7 +141,7 @@ public void head(Node source, int depth) { } public void tail(Node source, int depth) { - if (source instanceof Element && whitelist.isSafeTag(source.nodeName())) { + if (source instanceof Element && safelist.isSafeTag(source.nodeName())) { destination = destination.parent(); // would have descended, so pop destination stack } } @@ -151,12 +161,12 @@ private ElementMeta createSafeElement(Element sourceEl) { Attributes sourceAttrs = sourceEl.attributes(); for (Attribute sourceAttr : sourceAttrs) { - if (whitelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)) + if (safelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)) destAttrs.put(sourceAttr); else numDiscarded++; } - Attributes enforcedAttrs = whitelist.getEnforcedAttributes(sourceTag); + Attributes enforcedAttrs = safelist.getEnforcedAttributes(sourceTag); destAttrs.addAll(enforcedAttrs); return new ElementMeta(dest, numDiscarded); diff --git a/src/main/java/org/jsoup/safety/Safelist.java b/src/main/java/org/jsoup/safety/Safelist.java new file mode 100644 index 0000000000..91ad06b38c --- /dev/null +++ b/src/main/java/org/jsoup/safety/Safelist.java @@ -0,0 +1,655 @@ +package org.jsoup.safety; + +/* + Thank you to Ryan Grove (wonko.com) for the Ruby HTML cleaner http://github.com/rgrove/sanitize/, which inspired + this safe-list configuration, and the initial defaults. + */ + +import org.jsoup.helper.Validate; +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.jsoup.internal.Normalizer.lowerCase; + + +/** + Safe-lists define what HTML (elements and attributes) to allow through the cleaner. Everything else is removed. + <p> + Start with one of the defaults: + </p> + <ul> + <li>{@link #none} + <li>{@link #simpleText} + <li>{@link #basic} + <li>{@link #basicWithImages} + <li>{@link #relaxed} + </ul> + <p> + If you need to allow more through (please be careful!), tweak a base safelist with: + </p> + <ul> + <li>{@link #addTags} + <li>{@link #addAttributes} + <li>{@link #addEnforcedAttribute} + <li>{@link #addProtocols} + </ul> + <p> + You can remove any setting from an existing safelist with: + </p> + <ul> + <li>{@link #removeTags} + <li>{@link #removeAttributes} + <li>{@link #removeEnforcedAttribute} + <li>{@link #removeProtocols} + </ul> + + <p> + The cleaner and these safelist assume that you want to clean a <code>body</code> fragment of HTML (to add user + supplied HTML into a templated page), and not to clean a full HTML document. If the latter is the case, either wrap the + document HTML around the cleaned body HTML, or create a safelist that allows <code>html</code> and <code>head</code> + elements as appropriate. + </p> + <p> + If you are going to extend a safelist, please be very careful. Make sure you understand what attributes may lead to + XSS attack vectors. URL attributes are particularly vulnerable and require careful validation. See + http://ha.ckers.org/xss.html for some XSS attack examples. + </p> + + @author Jonathan Hedley + */ +public class Safelist { + private Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span] + private Map<TagName, Set<AttributeKey>> attributes; // tag -> attribute[]. allowed attributes [href] for a tag. + private Map<TagName, Map<AttributeKey, AttributeValue>> enforcedAttributes; // always set these attribute values + private Map<TagName, Map<AttributeKey, Set<Protocol>>> protocols; // allowed URL protocols for attributes + private boolean preserveRelativeLinks; // option to preserve relative links + + /** + This safelist allows only text nodes: all HTML will be stripped. + + @return safelist + */ + public static Safelist none() { + return new Safelist(); + } + + /** + This safelist allows only simple text formatting: <code>b, em, i, strong, u</code>. All other HTML (tags and + attributes) will be removed. + + @return safelist + */ + public static Safelist simpleText() { + return new Safelist() + .addTags("b", "em", "i", "strong", "u") + ; + } + + /** + <p> + This safelist allows a fuller range of text nodes: <code>a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, + ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul</code>, and appropriate attributes. + </p> + <p> + Links (<code>a</code> elements) can point to <code>http, https, ftp, mailto</code>, and have an enforced + <code>rel=nofollow</code> attribute. + </p> + <p> + Does not allow images. + </p> + + @return safelist + */ + public static Safelist basic() { + return new Safelist() + .addTags( + "a", "b", "blockquote", "br", "cite", "code", "dd", "dl", "dt", "em", + "i", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", "sub", + "sup", "u", "ul") + + .addAttributes("a", "href") + .addAttributes("blockquote", "cite") + .addAttributes("q", "cite") + + .addProtocols("a", "href", "ftp", "http", "https", "mailto") + .addProtocols("blockquote", "cite", "http", "https") + .addProtocols("cite", "cite", "http", "https") + + .addEnforcedAttribute("a", "rel", "nofollow") + ; + + } + + /** + This safelist allows the same text tags as {@link #basic}, and also allows <code>img</code> tags, with appropriate + attributes, with <code>src</code> pointing to <code>http</code> or <code>https</code>. + + @return safelist + */ + public static Safelist basicWithImages() { + return basic() + .addTags("img") + .addAttributes("img", "align", "alt", "height", "src", "title", "width") + .addProtocols("img", "src", "http", "https") + ; + } + + /** + This safelist allows a full range of text and structural body HTML: <code>a, b, blockquote, br, caption, cite, + code, col, colgroup, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span, strike, strong, sub, + sup, table, tbody, td, tfoot, th, thead, tr, u, ul</code> + <p> + Links do not have an enforced <code>rel=nofollow</code> attribute, but you can add that if desired. + </p> + + @return safelist + */ + public static Safelist relaxed() { + return new Safelist() + .addTags( + "a", "b", "blockquote", "br", "caption", "cite", "code", "col", + "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", + "i", "img", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", + "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", + "ul") + + .addAttributes("a", "href", "title") + .addAttributes("blockquote", "cite") + .addAttributes("col", "span", "width") + .addAttributes("colgroup", "span", "width") + .addAttributes("img", "align", "alt", "height", "src", "title", "width") + .addAttributes("ol", "start", "type") + .addAttributes("q", "cite") + .addAttributes("table", "summary", "width") + .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width") + .addAttributes( + "th", "abbr", "axis", "colspan", "rowspan", "scope", + "width") + .addAttributes("ul", "type") + + .addProtocols("a", "href", "ftp", "http", "https", "mailto") + .addProtocols("blockquote", "cite", "http", "https") + .addProtocols("cite", "cite", "http", "https") + .addProtocols("img", "src", "http", "https") + .addProtocols("q", "cite", "http", "https") + ; + } + + /** + Create a new, empty safelist. Generally it will be better to start with a default prepared safelist instead. + + @see #basic() + @see #basicWithImages() + @see #simpleText() + @see #relaxed() + */ + public Safelist() { + tagNames = new HashSet<>(); + attributes = new HashMap<>(); + enforcedAttributes = new HashMap<>(); + protocols = new HashMap<>(); + preserveRelativeLinks = false; + } + + /** + Deep copy an existing Safelist to a new Safelist. + @param copy the Safelist to copy + */ + public Safelist(Safelist copy) { + this(); + tagNames.addAll(copy.tagNames); + attributes.putAll(copy.attributes); + enforcedAttributes.putAll(copy.enforcedAttributes); + protocols.putAll(copy.protocols); + preserveRelativeLinks = copy.preserveRelativeLinks; + } + + /** + Add a list of allowed elements to a safelist. (If a tag is not allowed, it will be removed from the HTML.) + + @param tags tag names to allow + @return this (for chaining) + */ + public Safelist addTags(String... tags) { + Validate.notNull(tags); + + for (String tagName : tags) { + Validate.notEmpty(tagName); + tagNames.add(TagName.valueOf(tagName)); + } + return this; + } + + /** + Remove a list of allowed elements from a safelist. (If a tag is not allowed, it will be removed from the HTML.) + + @param tags tag names to disallow + @return this (for chaining) + */ + public Safelist removeTags(String... tags) { + Validate.notNull(tags); + + for(String tag: tags) { + Validate.notEmpty(tag); + TagName tagName = TagName.valueOf(tag); + + if(tagNames.remove(tagName)) { // Only look in sub-maps if tag was allowed + attributes.remove(tagName); + enforcedAttributes.remove(tagName); + protocols.remove(tagName); + } + } + return this; + } + + /** + Add a list of allowed attributes to a tag. (If an attribute is not allowed on an element, it will be removed.) + <p> + E.g.: <code>addAttributes("a", "href", "class")</code> allows <code>href</code> and <code>class</code> attributes + on <code>a</code> tags. + </p> + <p> + To make an attribute valid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g. + <code>addAttributes(":all", "class")</code>. + </p> + + @param tag The tag the attributes are for. The tag will be added to the allowed tag list if necessary. + @param attributes List of valid attributes for the tag + @return this (for chaining) + */ + public Safelist addAttributes(String tag, String... attributes) { + Validate.notEmpty(tag); + Validate.notNull(attributes); + Validate.isTrue(attributes.length > 0, "No attribute names supplied."); + + TagName tagName = TagName.valueOf(tag); + tagNames.add(tagName); + Set<AttributeKey> attributeSet = new HashSet<>(); + for (String key : attributes) { + Validate.notEmpty(key); + attributeSet.add(AttributeKey.valueOf(key)); + } + if (this.attributes.containsKey(tagName)) { + Set<AttributeKey> currentSet = this.attributes.get(tagName); + currentSet.addAll(attributeSet); + } else { + this.attributes.put(tagName, attributeSet); + } + return this; + } + + /** + Remove a list of allowed attributes from a tag. (If an attribute is not allowed on an element, it will be removed.) + <p> + E.g.: <code>removeAttributes("a", "href", "class")</code> disallows <code>href</code> and <code>class</code> + attributes on <code>a</code> tags. + </p> + <p> + To make an attribute invalid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g. + <code>removeAttributes(":all", "class")</code>. + </p> + + @param tag The tag the attributes are for. + @param attributes List of invalid attributes for the tag + @return this (for chaining) + */ + public Safelist removeAttributes(String tag, String... attributes) { + Validate.notEmpty(tag); + Validate.notNull(attributes); + Validate.isTrue(attributes.length > 0, "No attribute names supplied."); + + TagName tagName = TagName.valueOf(tag); + Set<AttributeKey> attributeSet = new HashSet<>(); + for (String key : attributes) { + Validate.notEmpty(key); + attributeSet.add(AttributeKey.valueOf(key)); + } + if(tagNames.contains(tagName) && this.attributes.containsKey(tagName)) { // Only look in sub-maps if tag was allowed + Set<AttributeKey> currentSet = this.attributes.get(tagName); + currentSet.removeAll(attributeSet); + + if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag + this.attributes.remove(tagName); + } + if(tag.equals(":all")) // Attribute needs to be removed from all individually set tags + for(TagName name: this.attributes.keySet()) { + Set<AttributeKey> currentSet = this.attributes.get(name); + currentSet.removeAll(attributeSet); + + if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag + this.attributes.remove(name); + } + return this; + } + + /** + Add an enforced attribute to a tag. An enforced attribute will always be added to the element. If the element + already has the attribute set, it will be overridden with this value. + <p> + E.g.: <code>addEnforcedAttribute("a", "rel", "nofollow")</code> will make all <code>a</code> tags output as + <code>&lt;a href="..." rel="nofollow"&gt;</code> + </p> + + @param tag The tag the enforced attribute is for. The tag will be added to the allowed tag list if necessary. + @param attribute The attribute name + @param value The enforced attribute value + @return this (for chaining) + */ + public Safelist addEnforcedAttribute(String tag, String attribute, String value) { + Validate.notEmpty(tag); + Validate.notEmpty(attribute); + Validate.notEmpty(value); + + TagName tagName = TagName.valueOf(tag); + tagNames.add(tagName); + AttributeKey attrKey = AttributeKey.valueOf(attribute); + AttributeValue attrVal = AttributeValue.valueOf(value); + + if (enforcedAttributes.containsKey(tagName)) { + enforcedAttributes.get(tagName).put(attrKey, attrVal); + } else { + Map<AttributeKey, AttributeValue> attrMap = new HashMap<>(); + attrMap.put(attrKey, attrVal); + enforcedAttributes.put(tagName, attrMap); + } + return this; + } + + /** + Remove a previously configured enforced attribute from a tag. + + @param tag The tag the enforced attribute is for. + @param attribute The attribute name + @return this (for chaining) + */ + public Safelist removeEnforcedAttribute(String tag, String attribute) { + Validate.notEmpty(tag); + Validate.notEmpty(attribute); + + TagName tagName = TagName.valueOf(tag); + if(tagNames.contains(tagName) && enforcedAttributes.containsKey(tagName)) { + AttributeKey attrKey = AttributeKey.valueOf(attribute); + Map<AttributeKey, AttributeValue> attrMap = enforcedAttributes.get(tagName); + attrMap.remove(attrKey); + + if(attrMap.isEmpty()) // Remove tag from enforced attribute map if no enforced attributes are present + enforcedAttributes.remove(tagName); + } + return this; + } + + /** + * Configure this Safelist to preserve relative links in an element's URL attribute, or convert them to absolute + * links. By default, this is <b>false</b>: URLs will be made absolute (e.g. start with an allowed protocol, like + * e.g. {@code http://}. + * <p> + * Note that when handling relative links, the input document must have an appropriate {@code base URI} set when + * parsing, so that the link's protocol can be confirmed. Regardless of the setting of the {@code preserve relative + * links} option, the link must be resolvable against the base URI to an allowed protocol; otherwise the attribute + * will be removed. + * </p> + * + * @param preserve {@code true} to allow relative links, {@code false} (default) to deny + * @return this Safelist, for chaining. + * @see #addProtocols + */ + public Safelist preserveRelativeLinks(boolean preserve) { + preserveRelativeLinks = preserve; + return this; + } + + /** + Add allowed URL protocols for an element's URL attribute. This restricts the possible values of the attribute to + URLs with the defined protocol. + <p> + E.g.: <code>addProtocols("a", "href", "ftp", "http", "https")</code> + </p> + <p> + To allow a link to an in-page URL anchor (i.e. <code>&lt;a href="#anchor"&gt;</code>, add a <code>#</code>:<br> + E.g.: <code>addProtocols("a", "href", "#")</code> + </p> + + @param tag Tag the URL protocol is for + @param attribute Attribute name + @param protocols List of valid protocols + @return this, for chaining + */ + public Safelist addProtocols(String tag, String attribute, String... protocols) { + Validate.notEmpty(tag); + Validate.notEmpty(attribute); + Validate.notNull(protocols); + + TagName tagName = TagName.valueOf(tag); + AttributeKey attrKey = AttributeKey.valueOf(attribute); + Map<AttributeKey, Set<Protocol>> attrMap; + Set<Protocol> protSet; + + if (this.protocols.containsKey(tagName)) { + attrMap = this.protocols.get(tagName); + } else { + attrMap = new HashMap<>(); + this.protocols.put(tagName, attrMap); + } + if (attrMap.containsKey(attrKey)) { + protSet = attrMap.get(attrKey); + } else { + protSet = new HashSet<>(); + attrMap.put(attrKey, protSet); + } + for (String protocol : protocols) { + Validate.notEmpty(protocol); + Protocol prot = Protocol.valueOf(protocol); + protSet.add(prot); + } + return this; + } + + /** + Remove allowed URL protocols for an element's URL attribute. If you remove all protocols for an attribute, that + attribute will allow any protocol. + <p> + E.g.: <code>removeProtocols("a", "href", "ftp")</code> + </p> + + @param tag Tag the URL protocol is for + @param attribute Attribute name + @param removeProtocols List of invalid protocols + @return this, for chaining + */ + public Safelist removeProtocols(String tag, String attribute, String... removeProtocols) { + Validate.notEmpty(tag); + Validate.notEmpty(attribute); + Validate.notNull(removeProtocols); + + TagName tagName = TagName.valueOf(tag); + AttributeKey attr = AttributeKey.valueOf(attribute); + + // make sure that what we're removing actually exists; otherwise can open the tag to any data and that can + // be surprising + Validate.isTrue(protocols.containsKey(tagName), "Cannot remove a protocol that is not set."); + Map<AttributeKey, Set<Protocol>> tagProtocols = protocols.get(tagName); + Validate.isTrue(tagProtocols.containsKey(attr), "Cannot remove a protocol that is not set."); + + Set<Protocol> attrProtocols = tagProtocols.get(attr); + for (String protocol : removeProtocols) { + Validate.notEmpty(protocol); + attrProtocols.remove(Protocol.valueOf(protocol)); + } + + if (attrProtocols.isEmpty()) { // Remove protocol set if empty + tagProtocols.remove(attr); + if (tagProtocols.isEmpty()) // Remove entry for tag if empty + protocols.remove(tagName); + } + return this; + } + + /** + * Test if the supplied tag is allowed by this safelist + * @param tag test tag + * @return true if allowed + */ + protected boolean isSafeTag(String tag) { + return tagNames.contains(TagName.valueOf(tag)); + } + + /** + * Test if the supplied attribute is allowed by this safelist for this tag + * @param tagName tag to consider allowing the attribute in + * @param el element under test, to confirm protocol + * @param attr attribute under test + * @return true if allowed + */ + protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) { + TagName tag = TagName.valueOf(tagName); + AttributeKey key = AttributeKey.valueOf(attr.getKey()); + + Set<AttributeKey> okSet = attributes.get(tag); + if (okSet != null && okSet.contains(key)) { + if (protocols.containsKey(tag)) { + Map<AttributeKey, Set<Protocol>> attrProts = protocols.get(tag); + // ok if not defined protocol; otherwise test + return !attrProts.containsKey(key) || testValidProtocol(el, attr, attrProts.get(key)); + } else { // attribute found, no protocols defined, so OK + return true; + } + } + // might be an enforced attribute? + Map<AttributeKey, AttributeValue> enforcedSet = enforcedAttributes.get(tag); + if (enforcedSet != null) { + Attributes expect = getEnforcedAttributes(tagName); + String attrKey = attr.getKey(); + if (expect.hasKeyIgnoreCase(attrKey)) { + return expect.getIgnoreCase(attrKey).equals(attr.getValue()); + } + } + // no attributes defined for tag, try :all tag + return !tagName.equals(":all") && isSafeAttribute(":all", el, attr); + } + + private boolean testValidProtocol(Element el, Attribute attr, Set<Protocol> protocols) { + // try to resolve relative urls to abs, and optionally update the attribute so output html has abs. + // rels without a baseuri get removed + String value = el.absUrl(attr.getKey()); + if (value.length() == 0) + value = attr.getValue(); // if it could not be made abs, run as-is to allow custom unknown protocols + if (!preserveRelativeLinks) + attr.setValue(value); + + for (Protocol protocol : protocols) { + String prot = protocol.toString(); + + if (prot.equals("#")) { // allows anchor links + if (isValidAnchor(value)) { + return true; + } else { + continue; + } + } + + prot += ":"; + + if (lowerCase(value).startsWith(prot)) { + return true; + } + } + return false; + } + + private boolean isValidAnchor(String value) { + return value.startsWith("#") && !value.matches(".*\\s.*"); + } + + Attributes getEnforcedAttributes(String tagName) { + Attributes attrs = new Attributes(); + TagName tag = TagName.valueOf(tagName); + if (enforcedAttributes.containsKey(tag)) { + Map<AttributeKey, AttributeValue> keyVals = enforcedAttributes.get(tag); + for (Map.Entry<AttributeKey, AttributeValue> entry : keyVals.entrySet()) { + attrs.put(entry.getKey().toString(), entry.getValue().toString()); + } + } + return attrs; + } + + // named types for config. All just hold strings, but here for my sanity. + + static class TagName extends TypedValue { + TagName(String value) { + super(value); + } + + static TagName valueOf(String value) { + return new TagName(value); + } + } + + static class AttributeKey extends TypedValue { + AttributeKey(String value) { + super(value); + } + + static AttributeKey valueOf(String value) { + return new AttributeKey(value); + } + } + + static class AttributeValue extends TypedValue { + AttributeValue(String value) { + super(value); + } + + static AttributeValue valueOf(String value) { + return new AttributeValue(value); + } + } + + static class Protocol extends TypedValue { + Protocol(String value) { + super(value); + } + + static Protocol valueOf(String value) { + return new Protocol(value); + } + } + + abstract static class TypedValue { + private String value; + + TypedValue(String value) { + Validate.notNull(value); + this.value = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + TypedValue other = (TypedValue) obj; + if (value == null) { + return other.value == null; + } else return value.equals(other.value); + } + + @Override + public String toString() { + return value; + } + } +} diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index 229ab36008..986cf4d0f6 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -1,643 +1,115 @@ package org.jsoup.safety; -/* - Thank you to Ryan Grove (wonko.com) for the Ruby HTML cleaner http://github.com/rgrove/sanitize/, which inspired - this whitelist configuration, and the initial defaults. - */ - -import org.jsoup.helper.Validate; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static org.jsoup.internal.Normalizer.lowerCase; - - /** - Whitelists define what HTML (elements and attributes) to allow through the cleaner. Everything else is removed. - <p> - Start with one of the defaults: - </p> - <ul> - <li>{@link #none} - <li>{@link #simpleText} - <li>{@link #basic} - <li>{@link #basicWithImages} - <li>{@link #relaxed} - </ul> + @deprecated As of release <code>v1.14.1</code>, this class is deprecated in favour of {@link Safelist}. The name has + been changed with the intent of promoting more inclusive language. {@link Safelist} is a drop-in replacement, and no + further changes other than updating the name in your code are required to cleanly migrate. This class will be + removed in <code>v1.15.1</code>. Until that release, this class acts as a shim to maintain code compatibility + (source and binary). <p> - If you need to allow more through (please be careful!), tweak a base whitelist with: - </p> - <ul> - <li>{@link #addTags} - <li>{@link #addAttributes} - <li>{@link #addEnforcedAttribute} - <li>{@link #addProtocols} - </ul> - <p> - You can remove any setting from an existing whitelist with: - </p> - <ul> - <li>{@link #removeTags} - <li>{@link #removeAttributes} - <li>{@link #removeEnforcedAttribute} - <li>{@link #removeProtocols} - </ul> - - <p> - The cleaner and these whitelists assume that you want to clean a <code>body</code> fragment of HTML (to add user - supplied HTML into a templated page), and not to clean a full HTML document. If the latter is the case, either wrap the - document HTML around the cleaned body HTML, or create a whitelist that allows <code>html</code> and <code>head</code> - elements as appropriate. - </p> - <p> - If you are going to extend a whitelist, please be very careful. Make sure you understand what attributes may lead to - XSS attack vectors. URL attributes are particularly vulnerable and require careful validation. See - http://ha.ckers.org/xss.html for some XSS attack examples. - </p> - - @author Jonathan Hedley - */ -public class Whitelist { - private Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span] - private Map<TagName, Set<AttributeKey>> attributes; // tag -> attribute[]. allowed attributes [href] for a tag. - private Map<TagName, Map<AttributeKey, AttributeValue>> enforcedAttributes; // always set these attribute values - private Map<TagName, Map<AttributeKey, Set<Protocol>>> protocols; // allowed URL protocols for attributes - private boolean preserveRelativeLinks; // option to preserve relative links - - /** - This whitelist allows only text nodes: all HTML will be stripped. - - @return whitelist - */ - public static Whitelist none() { - return new Whitelist(); + For a clear rationale of the removal of this change, please see + <a href="https://tools.ietf.org/html/draft-knodel-terminology-04" title="draft-knodel-terminology-04">Terminology, + Power, and Inclusive Language in Internet-Drafts and RFCs</a> */ +@Deprecated +public class Whitelist extends Safelist { + public Whitelist() { + super(); } - /** - This whitelist allows only simple text formatting: <code>b, em, i, strong, u</code>. All other HTML (tags and - attributes) will be removed. - - @return whitelist - */ - public static Whitelist simpleText() { - return new Whitelist() - .addTags("b", "em", "i", "strong", "u") - ; + public Whitelist(Safelist copy) { + super(copy); } - /** - <p> - This whitelist allows a fuller range of text nodes: <code>a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, - ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul</code>, and appropriate attributes. - </p> - <p> - Links (<code>a</code> elements) can point to <code>http, https, ftp, mailto</code>, and have an enforced - <code>rel=nofollow</code> attribute. - </p> - <p> - Does not allow images. - </p> - - @return whitelist - */ - public static Whitelist basic() { - return new Whitelist() - .addTags( - "a", "b", "blockquote", "br", "cite", "code", "dd", "dl", "dt", "em", - "i", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", "sub", - "sup", "u", "ul") - - .addAttributes("a", "href") - .addAttributes("blockquote", "cite") - .addAttributes("q", "cite") - - .addProtocols("a", "href", "ftp", "http", "https", "mailto") - .addProtocols("blockquote", "cite", "http", "https") - .addProtocols("cite", "cite", "http", "https") - - .addEnforcedAttribute("a", "rel", "nofollow") - ; - + static public Whitelist basic() { + return new Whitelist(Safelist.basic()); } - /** - This whitelist allows the same text tags as {@link #basic}, and also allows <code>img</code> tags, with appropriate - attributes, with <code>src</code> pointing to <code>http</code> or <code>https</code>. - - @return whitelist - */ - public static Whitelist basicWithImages() { - return basic() - .addTags("img") - .addAttributes("img", "align", "alt", "height", "src", "title", "width") - .addProtocols("img", "src", "http", "https") - ; + static public Whitelist basicWithImages() { + return new Whitelist(Safelist.basicWithImages()); } - /** - This whitelist allows a full range of text and structural body HTML: <code>a, b, blockquote, br, caption, cite, - code, col, colgroup, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span, strike, strong, sub, - sup, table, tbody, td, tfoot, th, thead, tr, u, ul</code> - <p> - Links do not have an enforced <code>rel=nofollow</code> attribute, but you can add that if desired. - </p> - - @return whitelist - */ - public static Whitelist relaxed() { - return new Whitelist() - .addTags( - "a", "b", "blockquote", "br", "caption", "cite", "code", "col", - "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", - "i", "img", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", - "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", - "ul") - - .addAttributes("a", "href", "title") - .addAttributes("blockquote", "cite") - .addAttributes("col", "span", "width") - .addAttributes("colgroup", "span", "width") - .addAttributes("img", "align", "alt", "height", "src", "title", "width") - .addAttributes("ol", "start", "type") - .addAttributes("q", "cite") - .addAttributes("table", "summary", "width") - .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width") - .addAttributes( - "th", "abbr", "axis", "colspan", "rowspan", "scope", - "width") - .addAttributes("ul", "type") - - .addProtocols("a", "href", "ftp", "http", "https", "mailto") - .addProtocols("blockquote", "cite", "http", "https") - .addProtocols("cite", "cite", "http", "https") - .addProtocols("img", "src", "http", "https") - .addProtocols("q", "cite", "http", "https") - ; + static public Whitelist none() { + return new Whitelist(Safelist.none()); } - /** - Create a new, empty whitelist. Generally it will be better to start with a default prepared whitelist instead. - - @see #basic() - @see #basicWithImages() - @see #simpleText() - @see #relaxed() - */ - public Whitelist() { - tagNames = new HashSet<>(); - attributes = new HashMap<>(); - enforcedAttributes = new HashMap<>(); - protocols = new HashMap<>(); - preserveRelativeLinks = false; + static public Whitelist relaxed() { + return new Whitelist(Safelist.relaxed()); } - /** - Add a list of allowed elements to a whitelist. (If a tag is not allowed, it will be removed from the HTML.) + static public Whitelist simpleText() { + return new Whitelist(Safelist.simpleText()); + } - @param tags tag names to allow - @return this (for chaining) - */ + @Override public Whitelist addTags(String... tags) { - Validate.notNull(tags); - - for (String tagName : tags) { - Validate.notEmpty(tagName); - tagNames.add(TagName.valueOf(tagName)); - } + super.addTags(tags); return this; } - /** - Remove a list of allowed elements from a whitelist. (If a tag is not allowed, it will be removed from the HTML.) - - @param tags tag names to disallow - @return this (for chaining) - */ + @Override public Whitelist removeTags(String... tags) { - Validate.notNull(tags); - - for(String tag: tags) { - Validate.notEmpty(tag); - TagName tagName = TagName.valueOf(tag); - - if(tagNames.remove(tagName)) { // Only look in sub-maps if tag was allowed - attributes.remove(tagName); - enforcedAttributes.remove(tagName); - protocols.remove(tagName); - } - } + super.removeTags(tags); return this; } - /** - Add a list of allowed attributes to a tag. (If an attribute is not allowed on an element, it will be removed.) - <p> - E.g.: <code>addAttributes("a", "href", "class")</code> allows <code>href</code> and <code>class</code> attributes - on <code>a</code> tags. - </p> - <p> - To make an attribute valid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g. - <code>addAttributes(":all", "class")</code>. - </p> - - @param tag The tag the attributes are for. The tag will be added to the allowed tag list if necessary. - @param attributes List of valid attributes for the tag - @return this (for chaining) - */ + @Override public Whitelist addAttributes(String tag, String... attributes) { - Validate.notEmpty(tag); - Validate.notNull(attributes); - Validate.isTrue(attributes.length > 0, "No attribute names supplied."); - - TagName tagName = TagName.valueOf(tag); - tagNames.add(tagName); - Set<AttributeKey> attributeSet = new HashSet<>(); - for (String key : attributes) { - Validate.notEmpty(key); - attributeSet.add(AttributeKey.valueOf(key)); - } - if (this.attributes.containsKey(tagName)) { - Set<AttributeKey> currentSet = this.attributes.get(tagName); - currentSet.addAll(attributeSet); - } else { - this.attributes.put(tagName, attributeSet); - } + super.addAttributes(tag, attributes); return this; } - /** - Remove a list of allowed attributes from a tag. (If an attribute is not allowed on an element, it will be removed.) - <p> - E.g.: <code>removeAttributes("a", "href", "class")</code> disallows <code>href</code> and <code>class</code> - attributes on <code>a</code> tags. - </p> - <p> - To make an attribute invalid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g. - <code>removeAttributes(":all", "class")</code>. - </p> - - @param tag The tag the attributes are for. - @param attributes List of invalid attributes for the tag - @return this (for chaining) - */ + @Override public Whitelist removeAttributes(String tag, String... attributes) { - Validate.notEmpty(tag); - Validate.notNull(attributes); - Validate.isTrue(attributes.length > 0, "No attribute names supplied."); - - TagName tagName = TagName.valueOf(tag); - Set<AttributeKey> attributeSet = new HashSet<>(); - for (String key : attributes) { - Validate.notEmpty(key); - attributeSet.add(AttributeKey.valueOf(key)); - } - if(tagNames.contains(tagName) && this.attributes.containsKey(tagName)) { // Only look in sub-maps if tag was allowed - Set<AttributeKey> currentSet = this.attributes.get(tagName); - currentSet.removeAll(attributeSet); - - if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag - this.attributes.remove(tagName); - } - if(tag.equals(":all")) // Attribute needs to be removed from all individually set tags - for(TagName name: this.attributes.keySet()) { - Set<AttributeKey> currentSet = this.attributes.get(name); - currentSet.removeAll(attributeSet); - - if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag - this.attributes.remove(name); - } + super.removeAttributes(tag, attributes); return this; } - /** - Add an enforced attribute to a tag. An enforced attribute will always be added to the element. If the element - already has the attribute set, it will be overridden with this value. - <p> - E.g.: <code>addEnforcedAttribute("a", "rel", "nofollow")</code> will make all <code>a</code> tags output as - <code>&lt;a href="..." rel="nofollow"&gt;</code> - </p> - - @param tag The tag the enforced attribute is for. The tag will be added to the allowed tag list if necessary. - @param attribute The attribute name - @param value The enforced attribute value - @return this (for chaining) - */ + @Override public Whitelist addEnforcedAttribute(String tag, String attribute, String value) { - Validate.notEmpty(tag); - Validate.notEmpty(attribute); - Validate.notEmpty(value); - - TagName tagName = TagName.valueOf(tag); - tagNames.add(tagName); - AttributeKey attrKey = AttributeKey.valueOf(attribute); - AttributeValue attrVal = AttributeValue.valueOf(value); - - if (enforcedAttributes.containsKey(tagName)) { - enforcedAttributes.get(tagName).put(attrKey, attrVal); - } else { - Map<AttributeKey, AttributeValue> attrMap = new HashMap<>(); - attrMap.put(attrKey, attrVal); - enforcedAttributes.put(tagName, attrMap); - } + super.addEnforcedAttribute(tag, attribute, value); return this; } - /** - Remove a previously configured enforced attribute from a tag. - - @param tag The tag the enforced attribute is for. - @param attribute The attribute name - @return this (for chaining) - */ + @Override public Whitelist removeEnforcedAttribute(String tag, String attribute) { - Validate.notEmpty(tag); - Validate.notEmpty(attribute); - - TagName tagName = TagName.valueOf(tag); - if(tagNames.contains(tagName) && enforcedAttributes.containsKey(tagName)) { - AttributeKey attrKey = AttributeKey.valueOf(attribute); - Map<AttributeKey, AttributeValue> attrMap = enforcedAttributes.get(tagName); - attrMap.remove(attrKey); - - if(attrMap.isEmpty()) // Remove tag from enforced attribute map if no enforced attributes are present - enforcedAttributes.remove(tagName); - } + super.removeEnforcedAttribute(tag, attribute); return this; } - /** - * Configure this Whitelist to preserve relative links in an element's URL attribute, or convert them to absolute - * links. By default, this is <b>false</b>: URLs will be made absolute (e.g. start with an allowed protocol, like - * e.g. {@code http://}. - * <p> - * Note that when handling relative links, the input document must have an appropriate {@code base URI} set when - * parsing, so that the link's protocol can be confirmed. Regardless of the setting of the {@code preserve relative - * links} option, the link must be resolvable against the base URI to an allowed protocol; otherwise the attribute - * will be removed. - * </p> - * - * @param preserve {@code true} to allow relative links, {@code false} (default) to deny - * @return this Whitelist, for chaining. - * @see #addProtocols - */ + @Override public Whitelist preserveRelativeLinks(boolean preserve) { - preserveRelativeLinks = preserve; + super.preserveRelativeLinks(preserve); return this; } - /** - Add allowed URL protocols for an element's URL attribute. This restricts the possible values of the attribute to - URLs with the defined protocol. - <p> - E.g.: <code>addProtocols("a", "href", "ftp", "http", "https")</code> - </p> - <p> - To allow a link to an in-page URL anchor (i.e. <code>&lt;a href="#anchor"&gt;</code>, add a <code>#</code>:<br> - E.g.: <code>addProtocols("a", "href", "#")</code> - </p> - - @param tag Tag the URL protocol is for - @param attribute Attribute name - @param protocols List of valid protocols - @return this, for chaining - */ + @Override public Whitelist addProtocols(String tag, String attribute, String... protocols) { - Validate.notEmpty(tag); - Validate.notEmpty(attribute); - Validate.notNull(protocols); - - TagName tagName = TagName.valueOf(tag); - AttributeKey attrKey = AttributeKey.valueOf(attribute); - Map<AttributeKey, Set<Protocol>> attrMap; - Set<Protocol> protSet; - - if (this.protocols.containsKey(tagName)) { - attrMap = this.protocols.get(tagName); - } else { - attrMap = new HashMap<>(); - this.protocols.put(tagName, attrMap); - } - if (attrMap.containsKey(attrKey)) { - protSet = attrMap.get(attrKey); - } else { - protSet = new HashSet<>(); - attrMap.put(attrKey, protSet); - } - for (String protocol : protocols) { - Validate.notEmpty(protocol); - Protocol prot = Protocol.valueOf(protocol); - protSet.add(prot); - } + super.addProtocols(tag, attribute, protocols); return this; } - /** - Remove allowed URL protocols for an element's URL attribute. If you remove all protocols for an attribute, that - attribute will allow any protocol. - <p> - E.g.: <code>removeProtocols("a", "href", "ftp")</code> - </p> - - @param tag Tag the URL protocol is for - @param attribute Attribute name - @param removeProtocols List of invalid protocols - @return this, for chaining - */ + @Override public Whitelist removeProtocols(String tag, String attribute, String... removeProtocols) { - Validate.notEmpty(tag); - Validate.notEmpty(attribute); - Validate.notNull(removeProtocols); - - TagName tagName = TagName.valueOf(tag); - AttributeKey attr = AttributeKey.valueOf(attribute); - - // make sure that what we're removing actually exists; otherwise can open the tag to any data and that can - // be surprising - Validate.isTrue(protocols.containsKey(tagName), "Cannot remove a protocol that is not set."); - Map<AttributeKey, Set<Protocol>> tagProtocols = protocols.get(tagName); - Validate.isTrue(tagProtocols.containsKey(attr), "Cannot remove a protocol that is not set."); - - Set<Protocol> attrProtocols = tagProtocols.get(attr); - for (String protocol : removeProtocols) { - Validate.notEmpty(protocol); - attrProtocols.remove(Protocol.valueOf(protocol)); - } - - if (attrProtocols.isEmpty()) { // Remove protocol set if empty - tagProtocols.remove(attr); - if (tagProtocols.isEmpty()) // Remove entry for tag if empty - protocols.remove(tagName); - } + super.removeProtocols(tag, attribute, removeProtocols); return this; } - /** - * Test if the supplied tag is allowed by this whitelist - * @param tag test tag - * @return true if allowed - */ + @Override protected boolean isSafeTag(String tag) { - return tagNames.contains(TagName.valueOf(tag)); + return super.isSafeTag(tag); } - /** - * Test if the supplied attribute is allowed by this whitelist for this tag - * @param tagName tag to consider allowing the attribute in - * @param el element under test, to confirm protocol - * @param attr attribute under test - * @return true if allowed - */ + @Override protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) { - TagName tag = TagName.valueOf(tagName); - AttributeKey key = AttributeKey.valueOf(attr.getKey()); - - Set<AttributeKey> okSet = attributes.get(tag); - if (okSet != null && okSet.contains(key)) { - if (protocols.containsKey(tag)) { - Map<AttributeKey, Set<Protocol>> attrProts = protocols.get(tag); - // ok if not defined protocol; otherwise test - return !attrProts.containsKey(key) || testValidProtocol(el, attr, attrProts.get(key)); - } else { // attribute found, no protocols defined, so OK - return true; - } - } - // might be an enforced attribute? - Map<AttributeKey, AttributeValue> enforcedSet = enforcedAttributes.get(tag); - if (enforcedSet != null) { - Attributes expect = getEnforcedAttributes(tagName); - String attrKey = attr.getKey(); - if (expect.hasKeyIgnoreCase(attrKey)) { - return expect.getIgnoreCase(attrKey).equals(attr.getValue()); - } - } - // no attributes defined for tag, try :all tag - return !tagName.equals(":all") && isSafeAttribute(":all", el, attr); - } - - private boolean testValidProtocol(Element el, Attribute attr, Set<Protocol> protocols) { - // try to resolve relative urls to abs, and optionally update the attribute so output html has abs. - // rels without a baseuri get removed - String value = el.absUrl(attr.getKey()); - if (value.length() == 0) - value = attr.getValue(); // if it could not be made abs, run as-is to allow custom unknown protocols - if (!preserveRelativeLinks) - attr.setValue(value); - - for (Protocol protocol : protocols) { - String prot = protocol.toString(); - - if (prot.equals("#")) { // allows anchor links - if (isValidAnchor(value)) { - return true; - } else { - continue; - } - } - - prot += ":"; - - if (lowerCase(value).startsWith(prot)) { - return true; - } - } - return false; - } - - private boolean isValidAnchor(String value) { - return value.startsWith("#") && !value.matches(".*\\s.*"); + return super.isSafeAttribute(tagName, el, attr); } + @Override Attributes getEnforcedAttributes(String tagName) { - Attributes attrs = new Attributes(); - TagName tag = TagName.valueOf(tagName); - if (enforcedAttributes.containsKey(tag)) { - Map<AttributeKey, AttributeValue> keyVals = enforcedAttributes.get(tag); - for (Map.Entry<AttributeKey, AttributeValue> entry : keyVals.entrySet()) { - attrs.put(entry.getKey().toString(), entry.getValue().toString()); - } - } - return attrs; - } - - // named types for config. All just hold strings, but here for my sanity. - - static class TagName extends TypedValue { - TagName(String value) { - super(value); - } - - static TagName valueOf(String value) { - return new TagName(value); - } - } - - static class AttributeKey extends TypedValue { - AttributeKey(String value) { - super(value); - } - - static AttributeKey valueOf(String value) { - return new AttributeKey(value); - } - } - - static class AttributeValue extends TypedValue { - AttributeValue(String value) { - super(value); - } - - static AttributeValue valueOf(String value) { - return new AttributeValue(value); - } - } - - static class Protocol extends TypedValue { - Protocol(String value) { - super(value); - } - - static Protocol valueOf(String value) { - return new Protocol(value); - } - } - - abstract static class TypedValue { - private String value; - - TypedValue(String value) { - Validate.notNull(value); - this.value = value; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - TypedValue other = (TypedValue) obj; - if (value == null) { - return other.value == null; - } else return value.equals(other.value); - } - - @Override - public String toString() { - return value; - } + return super.getEnforcedAttributes(tagName); } } - diff --git a/src/main/java/org/jsoup/safety/package-info.java b/src/main/java/org/jsoup/safety/package-info.java index ac890f0607..26b4b701bb 100644 --- a/src/main/java/org/jsoup/safety/package-info.java +++ b/src/main/java/org/jsoup/safety/package-info.java @@ -1,4 +1,4 @@ /** - Contains the jsoup HTML cleaner, and whitelist definitions. + Contains the jsoup HTML cleaner, and safelist definitions. */ package org.jsoup.safety; diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index bbeac18a06..0fa25e11b1 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -1,31 +1,31 @@ <!DOCTYPE html> <html> <head> - <title>jsoup Javadoc overview</title> + <title>jsoup Javadoc overview</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>jsoup: Java HTML parser that makes sense of real-world HTML soup.</h1> -<p><b>jsoup</b> is a Java library for working with real-world HTML. It provides a very convenient API -for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods.</p> +<p><b>jsoup</b> is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs + and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors.</p> -<p>jsoup implements the <a href="http://whatwg.org/html">WHATWG HTML</a> specification, and parses HTML to the same DOM -as modern browsers do.</p> +<p>jsoup implements the <a href="https://html.spec.whatwg.org/multipage/">WHATWG HTML</a> specification, and parses HTML to the same DOM + as modern browsers do.</p> <ul> -<li>parse HTML from a URL, file, or string -<li>find and extract data, using DOM traversal or CSS selectors -<li>manipulate the HTML elements, attributes, and text -<li>clean user-submitted content against a safe white-list, to prevent XSS -<li>output tidy HTML + <li>parse HTML from a URL, file, or string + <li>find and extract data, using DOM traversal or CSS selectors + <li>manipulate the HTML elements, attributes, and text + <li>clean user-submitted content against a safelist, to prevent XSS + <li>output tidy HTML </ul> <p>jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, -to invalid tag-soup; jsoup will create a sensible parse tree.</p> + to invalid tag-soup; jsoup will create a sensible parse tree.</p> <p>See <a href="https://jsoup.org/"><b>jsoup.org</b></a> for downloads, documentation, and examples...</p> -@author <a href="http://jonathanhedley.com/">Jonathan Hedley</a> +@author <a href="https://jonathanhedley.com/">Jonathan Hedley</a> </body> </html> diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 8eb27cb387..36d4a31085 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -5,7 +5,7 @@ import org.jsoup.integration.ParseTest; import org.jsoup.internal.StringUtil; import org.jsoup.nodes.*; -import org.jsoup.safety.Whitelist; +import org.jsoup.safety.Safelist; import org.jsoup.select.Elements; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -1158,8 +1158,8 @@ public void testInvalidTableContents() throws IOException { parser.parseInput(html, ""); assertEquals(0, parser.getErrors().size()); - assertTrue(Jsoup.isValid(html, Whitelist.basic())); - String clean = Jsoup.clean(html, Whitelist.basic()); + assertTrue(Jsoup.isValid(html, Safelist.basic())); + String clean = Jsoup.clean(html, Safelist.basic()); assertEquals("<p>test<br>test<br></p>", clean); } @@ -1170,8 +1170,8 @@ public void testInvalidTableContents() throws IOException { assertEquals(1, parser.getErrors().size()); assertEquals("18: Tag cannot be self closing; not a void tag", parser.getErrors().get(0).toString()); - assertFalse(Jsoup.isValid(html, Whitelist.relaxed())); - String clean = Jsoup.clean(html, Whitelist.relaxed()); + assertFalse(Jsoup.isValid(html, Safelist.relaxed())); + String clean = Jsoup.clean(html, Safelist.relaxed()); assertEquals("<p>test</p> <div></div> <div> Two </div>", StringUtil.normaliseWhitespace(clean)); } @@ -1294,7 +1294,7 @@ public void indentRegardlessOfCase() { public void testH20() { // https://github.com/jhy/jsoup/issues/731 String html = "H<sub>2</sub>O"; - String clean = Jsoup.clean(html, Whitelist.basic()); + String clean = Jsoup.clean(html, Safelist.basic()); assertEquals("H<sub>2</sub>O", clean); Document doc = Jsoup.parse(html); @@ -1305,7 +1305,7 @@ public void testH20() { public void testUNewlines() { // https://github.com/jhy/jsoup/issues/851 String html = "t<u>es</u>t <b>on</b> <i>f</i><u>ir</u>e"; - String clean = Jsoup.clean(html, Whitelist.basic()); + String clean = Jsoup.clean(html, Safelist.basic()); assertEquals("t<u>es</u>t <b>on</b> <i>f</i><u>ir</u>e", clean); Document doc = Jsoup.parse(html); diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 707313f8f4..bce555d914 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -18,21 +18,21 @@ public class CleanerTest { @Test public void simpleBehaviourTest() { String h = "<div><p class=foo><a href='http://evil.com'>Hello <b id=bar>there</b>!</a></div>"; - String cleanHtml = Jsoup.clean(h, Whitelist.simpleText()); + String cleanHtml = Jsoup.clean(h, Safelist.simpleText()); assertEquals("Hello <b>there</b>!", TextUtil.stripNewlines(cleanHtml)); } @Test public void simpleBehaviourTest2() { String h = "Hello <b>there</b>!"; - String cleanHtml = Jsoup.clean(h, Whitelist.simpleText()); + String cleanHtml = Jsoup.clean(h, Safelist.simpleText()); assertEquals("Hello <b>there</b>!", TextUtil.stripNewlines(cleanHtml)); } @Test public void basicBehaviourTest() { String h = "<div><p><a href='javascript:sendAllMoney()'>Dodgy</a> <A HREF='HTTP://nice.com'>Nice</a></p><blockquote>Hello</blockquote>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basic()); + String cleanHtml = Jsoup.clean(h, Safelist.basic()); assertEquals("<p><a rel=\"nofollow\">Dodgy</a> <a href=\"http://nice.com\" rel=\"nofollow\">Nice</a></p><blockquote>Hello</blockquote>", TextUtil.stripNewlines(cleanHtml)); @@ -40,33 +40,33 @@ public class CleanerTest { @Test public void basicWithImagesTest() { String h = "<div><p><img src='http://example.com/' alt=Image></p><p><img src='ftp://ftp.example.com'></p></div>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basicWithImages()); + String cleanHtml = Jsoup.clean(h, Safelist.basicWithImages()); assertEquals("<p><img src=\"http://example.com/\" alt=\"Image\"></p><p><img></p>", TextUtil.stripNewlines(cleanHtml)); } @Test public void testRelaxed() { String h = "<h1>Head</h1><table><tr><td>One<td>Two</td></tr></table>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("<h1>Head</h1><table><tbody><tr><td>One</td><td>Two</td></tr></tbody></table>", TextUtil.stripNewlines(cleanHtml)); } @Test public void testRemoveTags() { String h = "<div><p><A HREF='HTTP://nice.com'>Nice</a></p><blockquote>Hello</blockquote>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basic().removeTags("a")); + String cleanHtml = Jsoup.clean(h, Safelist.basic().removeTags("a")); assertEquals("<p>Nice</p><blockquote>Hello</blockquote>", TextUtil.stripNewlines(cleanHtml)); } @Test public void testRemoveAttributes() { String h = "<div><p>Nice</p><blockquote cite='http://example.com/quotations'>Hello</blockquote>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basic().removeAttributes("blockquote", "cite")); + String cleanHtml = Jsoup.clean(h, Safelist.basic().removeAttributes("blockquote", "cite")); assertEquals("<p>Nice</p><blockquote>Hello</blockquote>", TextUtil.stripNewlines(cleanHtml)); } @Test public void testRemoveEnforcedAttributes() { String h = "<div><p><A HREF='HTTP://nice.com'>Nice</a></p><blockquote>Hello</blockquote>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basic().removeEnforcedAttribute("a", "rel")); + String cleanHtml = Jsoup.clean(h, Safelist.basic().removeEnforcedAttribute("a", "rel")); assertEquals("<p><a href=\"http://nice.com\">Nice</a></p><blockquote>Hello</blockquote>", TextUtil.stripNewlines(cleanHtml)); @@ -74,53 +74,53 @@ public class CleanerTest { @Test public void testRemoveProtocols() { String h = "<p>Contact me <a href='mailto:info@example.com'>here</a></p>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basic().removeProtocols("a", "href", "ftp", "mailto")); + String cleanHtml = Jsoup.clean(h, Safelist.basic().removeProtocols("a", "href", "ftp", "mailto")); assertEquals("<p>Contact me <a rel=\"nofollow\">here</a></p>", TextUtil.stripNewlines(cleanHtml)); } @MultiLocaleTest - public void whitelistedProtocolShouldBeRetained(Locale locale) { + public void safeListedProtocolShouldBeRetained(Locale locale) { Locale.setDefault(locale); - Whitelist whitelist = Whitelist.none() + Safelist safelist = Safelist.none() .addTags("a") .addAttributes("a", "href") .addProtocols("a", "href", "something"); - String cleanHtml = Jsoup.clean("<a href=\"SOMETHING://x\"></a>", whitelist); + String cleanHtml = Jsoup.clean("<a href=\"SOMETHING://x\"></a>", safelist); assertEquals("<a href=\"SOMETHING://x\"></a>", TextUtil.stripNewlines(cleanHtml)); } @Test public void testDropComments() { String h = "<p>Hello<!-- no --></p>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("<p>Hello</p>", cleanHtml); } @Test public void testDropXmlProc() { String h = "<?import namespace=\"xss\"><p>Hello</p>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("<p>Hello</p>", cleanHtml); } @Test public void testDropScript() { String h = "<SCRIPT SRC=//ha.ckers.org/.j><SCRIPT>alert(/XSS/.source)</SCRIPT>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("", cleanHtml); } @Test public void testDropImageScript() { String h = "<IMG SRC=\"javascript:alert('XSS')\">"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("<img>", cleanHtml); } @Test public void testCleanJavascriptHref() { String h = "<A HREF=\"javascript:document.location='http://www.google.com/'\">XSS</A>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("<a>XSS</a>", cleanHtml); } @@ -128,15 +128,15 @@ public void whitelistedProtocolShouldBeRetained(Locale locale) { String validAnchor = "<a href=\"#valid\">Valid anchor</a>"; String invalidAnchor = "<a href=\"#anchor with spaces\">Invalid anchor</a>"; - // A Whitelist that does not allow anchors will strip them out. - String cleanHtml = Jsoup.clean(validAnchor, Whitelist.relaxed()); + // A Safelist that does not allow anchors will strip them out. + String cleanHtml = Jsoup.clean(validAnchor, Safelist.relaxed()); assertEquals("<a>Valid anchor</a>", cleanHtml); - cleanHtml = Jsoup.clean(invalidAnchor, Whitelist.relaxed()); + cleanHtml = Jsoup.clean(invalidAnchor, Safelist.relaxed()); assertEquals("<a>Invalid anchor</a>", cleanHtml); - // A Whitelist that allows them will keep them. - Whitelist relaxedWithAnchor = Whitelist.relaxed().addProtocols("a", "href", "#"); + // A Safelist that allows them will keep them. + Safelist relaxedWithAnchor = Safelist.relaxed().addProtocols("a", "href", "#"); cleanHtml = Jsoup.clean(validAnchor, relaxedWithAnchor); assertEquals(validAnchor, cleanHtml); @@ -148,13 +148,13 @@ public void whitelistedProtocolShouldBeRetained(Locale locale) { @Test public void testDropsUnknownTags() { String h = "<p><custom foo=true>Test</custom></p>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + String cleanHtml = Jsoup.clean(h, Safelist.relaxed()); assertEquals("<p>Test</p>", cleanHtml); } @Test public void testHandlesEmptyAttributes() { String h = "<img alt=\"\" src= unknown=''>"; - String cleanHtml = Jsoup.clean(h, Whitelist.basicWithImages()); + String cleanHtml = Jsoup.clean(h, Safelist.basicWithImages()); assertEquals("<img alt=\"\">", cleanHtml); } @@ -168,74 +168,74 @@ public void whitelistedProtocolShouldBeRetained(Locale locale) { String nok5 = "<p>Test <b><a href='http://example.com/' rel='nofollowme'>OK</a></b></p>"; String nok6 = "<p>Test <b><a href='http://example.com/'>OK</b></p>"; // missing close tag String nok7 = "</div>What"; - assertTrue(Jsoup.isValid(ok, Whitelist.basic())); - assertTrue(Jsoup.isValid(ok1, Whitelist.basic())); - assertFalse(Jsoup.isValid(nok1, Whitelist.basic())); - assertFalse(Jsoup.isValid(nok2, Whitelist.basic())); - assertFalse(Jsoup.isValid(nok3, Whitelist.basic())); - assertFalse(Jsoup.isValid(nok4, Whitelist.basic())); - assertFalse(Jsoup.isValid(nok5, Whitelist.basic())); - assertFalse(Jsoup.isValid(nok6, Whitelist.basic())); - assertFalse(Jsoup.isValid(ok, Whitelist.none())); - assertFalse(Jsoup.isValid(nok7, Whitelist.basic())); + assertTrue(Jsoup.isValid(ok, Safelist.basic())); + assertTrue(Jsoup.isValid(ok1, Safelist.basic())); + assertFalse(Jsoup.isValid(nok1, Safelist.basic())); + assertFalse(Jsoup.isValid(nok2, Safelist.basic())); + assertFalse(Jsoup.isValid(nok3, Safelist.basic())); + assertFalse(Jsoup.isValid(nok4, Safelist.basic())); + assertFalse(Jsoup.isValid(nok5, Safelist.basic())); + assertFalse(Jsoup.isValid(nok6, Safelist.basic())); + assertFalse(Jsoup.isValid(ok, Safelist.none())); + assertFalse(Jsoup.isValid(nok7, Safelist.basic())); } @Test public void testIsValidDocument() { String ok = "<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>Hello</p></body><html>"; String nok = "<html><head><script>woops</script><title>Hello</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>Hello</p></body><html>"; - Whitelist relaxed = Whitelist.relaxed(); + Safelist relaxed = Safelist.relaxed(); Cleaner cleaner = new Cleaner(relaxed); Document okDoc = Jsoup.parse(ok); assertTrue(cleaner.isValid(okDoc)); assertFalse(cleaner.isValid(Jsoup.parse(nok))); - assertFalse(new Cleaner(Whitelist.none()).isValid(okDoc)); + assertFalse(new Cleaner(Safelist.none()).isValid(okDoc)); } @Test public void resolvesRelativeLinks() { String html = "<a href='/foo'>Link</a><img src='/bar'>"; - String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages()); + String clean = Jsoup.clean(html, "http://example.com/", Safelist.basicWithImages()); assertEquals("<a href=\"http://example.com/foo\" rel=\"nofollow\">Link</a>\n<img src=\"http://example.com/bar\">", clean); } @Test public void preservesRelativeLinksIfConfigured() { String html = "<a href='/foo'>Link</a><img src='/bar'> <img src='javascript:alert()'>"; - String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages().preserveRelativeLinks(true)); + String clean = Jsoup.clean(html, "http://example.com/", Safelist.basicWithImages().preserveRelativeLinks(true)); assertEquals("<a href=\"/foo\" rel=\"nofollow\">Link</a>\n<img src=\"/bar\"> \n<img>", clean); } @Test public void dropsUnresolvableRelativeLinks() { String html = "<a href='/foo'>Link</a>"; - String clean = Jsoup.clean(html, Whitelist.basic()); + String clean = Jsoup.clean(html, Safelist.basic()); assertEquals("<a rel=\"nofollow\">Link</a>", clean); } @Test public void handlesCustomProtocols() { String html = "<img src='cid:12345' /> <img src='data:gzzt' />"; - String dropped = Jsoup.clean(html, Whitelist.basicWithImages()); + String dropped = Jsoup.clean(html, Safelist.basicWithImages()); assertEquals("<img> \n<img>", dropped); - String preserved = Jsoup.clean(html, Whitelist.basicWithImages().addProtocols("img", "src", "cid", "data")); + String preserved = Jsoup.clean(html, Safelist.basicWithImages().addProtocols("img", "src", "cid", "data")); assertEquals("<img src=\"cid:12345\"> \n<img src=\"data:gzzt\">", preserved); } @Test public void handlesAllPseudoTag() { String html = "<p class='foo' src='bar'><a class='qux'>link</a></p>"; - Whitelist whitelist = new Whitelist() + Safelist safelist = new Safelist() .addAttributes(":all", "class") .addAttributes("p", "style") .addTags("p", "a"); - String clean = Jsoup.clean(html, whitelist); + String clean = Jsoup.clean(html, safelist); assertEquals("<p class=\"foo\"><a class=\"qux\">link</a></p>", clean); } @Test public void addsTagOnAttributesIfNotSet() { String html = "<p class='foo' src='bar'>One</p>"; - Whitelist whitelist = new Whitelist() + Safelist safelist = new Safelist() .addAttributes("p", "class"); - // ^^ whitelist does not have explicit tag add for p, inferred from add attributes. - String clean = Jsoup.clean(html, whitelist); + // ^^ safelist does not have explicit tag add for p, inferred from add attributes. + String clean = Jsoup.clean(html, safelist); assertEquals("<p class=\"foo\">One</p>", clean); } @@ -247,8 +247,8 @@ public void whitelistedProtocolShouldBeRetained(Locale locale) { os.charset("ascii"); String html = "<div><p>&bernou;</p></div>"; - String customOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed(), os); - String defaultOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed()); + String customOut = Jsoup.clean(html, "http://foo.com/", Safelist.relaxed(), os); + String defaultOut = Jsoup.clean(html, "http://foo.com/", Safelist.relaxed()); assertNotSame(defaultOut, customOut); assertEquals("<div><p>&Bscr;</p></div>", customOut); // entities now prefers shorted names if aliased @@ -258,37 +258,37 @@ public void whitelistedProtocolShouldBeRetained(Locale locale) { os.charset("ASCII"); os.escapeMode(Entities.EscapeMode.base); - String customOut2 = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed(), os); + String customOut2 = Jsoup.clean(html, "http://foo.com/", Safelist.relaxed(), os); assertEquals("<div><p>&#x212c;</p></div>", customOut2); } @Test public void handlesFramesets() { String dirty = "<html><head><script></script><noscript></noscript> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><frameset><frame src=\"foo\" /><frame src=\"foo\" /></frameset></html>"; - String clean = Jsoup.clean(dirty, Whitelist.basic()); + String clean = Jsoup.clean(dirty, Safelist.basic()); assertEquals("", clean); // nothing good can come out of that Document dirtyDoc = Jsoup.parse(dirty); - Document cleanDoc = new Cleaner(Whitelist.basic()).clean(dirtyDoc); + Document cleanDoc = new Cleaner(Safelist.basic()).clean(dirtyDoc); assertNotNull(cleanDoc); assertEquals(0, cleanDoc.body().childNodeSize()); } @Test public void cleansInternationalText() { - assertEquals("привет", Jsoup.clean("привет", Whitelist.none())); + assertEquals("привет", Jsoup.clean("привет", Safelist.none())); } @Test - public void testScriptTagInWhiteList() { - Whitelist whitelist = Whitelist.relaxed(); - whitelist.addTags( "script" ); - assertTrue( Jsoup.isValid("Hello<script>alert('Doh')</script>World !", whitelist ) ); + public void testScriptTagInSafeList() { + Safelist safelist = Safelist.relaxed(); + safelist.addTags( "script" ); + assertTrue( Jsoup.isValid("Hello<script>alert('Doh')</script>World !", safelist) ); } @Test public void bailsIfRemovingProtocolThatsNotSet() { assertThrows(IllegalArgumentException.class, () -> { // a case that came up on the email list - Whitelist w = Whitelist.none(); + Safelist w = Safelist.none(); // note no add tag, and removing protocol without adding first w.addAttributes("a", "href"); @@ -298,20 +298,20 @@ public void bailsIfRemovingProtocolThatsNotSet() { @Test public void handlesControlCharactersAfterTagName() { String html = "<a/\06>"; - String clean = Jsoup.clean(html, Whitelist.basic()); + String clean = Jsoup.clean(html, Safelist.basic()); assertEquals("<a rel=\"nofollow\"></a>", clean); } @Test public void handlesAttributesWithNoValue() { // https://github.com/jhy/jsoup/issues/973 - String clean = Jsoup.clean("<a href>Clean</a>", Whitelist.basic()); + String clean = Jsoup.clean("<a href>Clean</a>", Safelist.basic()); assertEquals("<a rel=\"nofollow\">Clean</a>", clean); } @Test public void handlesNoHrefAttribute() { String dirty = "<a>One</a> <a href>Two</a>"; - Whitelist relaxedWithAnchor = Whitelist.relaxed().addProtocols("a", "href", "#"); + Safelist relaxedWithAnchor = Safelist.relaxed().addProtocols("a", "href", "#"); String clean = Jsoup.clean(dirty, relaxedWithAnchor); assertEquals("<a>One</a> <a>Two</a>", clean); } @@ -319,7 +319,7 @@ public void bailsIfRemovingProtocolThatsNotSet() { @Test public void handlesNestedQuotesInAttribute() { // https://github.com/jhy/jsoup/issues/1243 - no repro String orig = "<div style=\"font-family: 'Calibri'\">Will (not) fail</div>"; - Whitelist allow = Whitelist.relaxed() + Safelist allow = Safelist.relaxed() .addAttributes("div", "style"); String clean = Jsoup.clean(orig, allow); diff --git a/src/test/java/org/jsoup/safety/CompatibilityTests.java b/src/test/java/org/jsoup/safety/CompatibilityTests.java new file mode 100644 index 0000000000..7586950d1f --- /dev/null +++ b/src/test/java/org/jsoup/safety/CompatibilityTests.java @@ -0,0 +1,99 @@ +package org.jsoup.safety; + +import org.jsoup.Jsoup; +import org.jsoup.MultiLocaleExtension; +import org.jsoup.TextUtil; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Entities; +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.*; + +/** + Tests for the deprecated {@link org.jsoup.safety.Whitelist} class source compatibility. Will be removed in + <code>v.1.15.1</code>. No net new tests here so safe to blow up. + */ +public class CompatibilityTests { + @Test + public void resolvesRelativeLinks() { + String html = "<a href='/foo'>Link</a><img src='/bar'>"; + String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages()); + assertEquals("<a href=\"http://example.com/foo\" rel=\"nofollow\">Link</a>\n<img src=\"http://example.com/bar\">", clean); + } + + @Test + public void testDropsUnknownTags() { + String h = "<p><custom foo=true>Test</custom></p>"; + String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); + assertEquals("<p>Test</p>", cleanHtml); + } + + @Test + public void preservesRelativeLinksIfConfigured() { + String html = "<a href='/foo'>Link</a><img src='/bar'> <img src='javascript:alert()'>"; + String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages().preserveRelativeLinks(true)); + assertEquals("<a href=\"/foo\" rel=\"nofollow\">Link</a>\n<img src=\"/bar\"> \n<img>", clean); + } + + @Test + public void handlesCustomProtocols() { + String html = "<img src='cid:12345' /> <img src='data:gzzt' />"; + String dropped = Jsoup.clean(html, Whitelist.basicWithImages()); + assertEquals("<img> \n<img>", dropped); + + String preserved = Jsoup.clean(html, Whitelist.basicWithImages().addProtocols("img", "src", "cid", "data")); + assertEquals("<img src=\"cid:12345\"> \n<img src=\"data:gzzt\">", preserved); + } + + @Test + public void handlesFramesets() { + String dirty = "<html><head><script></script><noscript></noscript> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><frameset><frame src=\"foo\" /><frame src=\"foo\" /></frameset></html>"; + String clean = Jsoup.clean(dirty, Whitelist.basic()); + assertEquals("", clean); // nothing good can come out of that + + Document dirtyDoc = Jsoup.parse(dirty); + Document cleanDoc = new Cleaner(Whitelist.basic()).clean(dirtyDoc); + assertNotNull(cleanDoc); + assertEquals(0, cleanDoc.body().childNodeSize()); + } + + @Test + public void supplyOutputSettings() { + // test that one can override the default document output settings + Document.OutputSettings os = new Document.OutputSettings(); + os.prettyPrint(false); + os.escapeMode(Entities.EscapeMode.extended); + os.charset("ascii"); + + String html = "<div><p>&bernou;</p></div>"; + String customOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed(), os); + String defaultOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed()); + assertNotSame(defaultOut, customOut); + + assertEquals("<div><p>&Bscr;</p></div>", customOut); // entities now prefers shorted names if aliased + assertEquals("<div>\n" + + " <p>ℬ</p>\n" + + "</div>", defaultOut); + + os.charset("ASCII"); + os.escapeMode(Entities.EscapeMode.base); + String customOut2 = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed(), os); + assertEquals("<div><p>&#x212c;</p></div>", customOut2); + } + + @MultiLocaleExtension.MultiLocaleTest + public void safeListedProtocolShouldBeRetained(Locale locale) { + Locale.setDefault(locale); + + Whitelist safelist = Whitelist.none() + .addTags("a") + .addAttributes("a", "href") + .addProtocols("a", "href", "something"); + + String cleanHtml = Jsoup.clean("<a href=\"SOMETHING://x\"></a>", safelist); + + assertEquals("<a href=\"SOMETHING://x\"></a>", TextUtil.stripNewlines(cleanHtml)); + } +} From dc33b400cea01d76905ddadc12e5125427cbc264 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 21:38:51 +1100 Subject: [PATCH 456/774] Specify previous version for compat tests --- CHANGES | 1 + pom.xml | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGES b/CHANGES index 76e2d8905d..a323870ed9 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ jsoup changelog * Improvement: renamed the Whitelist class to Safelist, with the goal of more inclusive language. A shim is provided for backwards compatibility (source and binary). This shim is marked as deprecated and will be removed in the jsoup 1.15.1 release. + <https://github.com/jhy/jsoup/pull/1464> * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). diff --git a/pom.xml b/pom.xml index d0d0000cb1..c7328e0519 100644 --- a/pom.xml +++ b/pom.xml @@ -184,8 +184,18 @@ <artifactId>japicmp-maven-plugin</artifactId> <version>0.14.4</version> <configuration> + <!-- hard code previous version; can't detect when running stateless on build server --> + <oldVersion> + <dependency> + <groupId>org.jsoup</groupId> + <artifactId>jsoup</artifactId> + <version>1.13.1</version> + <type>jar</type> + </dependency> + </oldVersion> <parameter> <!-- jsoup policy is ok to remove deprecated methods on minor but not builds. will need to temp remove on bump to 1.15.1 and manually validate --> + <onlyModified>true</onlyModified> <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> </parameter> From 9edccb17aec39e0c4d1182dac88b91c634fdf456 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 22:23:00 +1100 Subject: [PATCH 457/774] Enable "restricted" headers, so that we can set Sec-Fetch-Mode etc Fixes #1461 --- CHANGES | 4 +++ .../java/org/jsoup/helper/HttpConnection.java | 4 +++ .../org/jsoup/integration/ConnectTest.java | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/CHANGES b/CHANGES index a323870ed9..491fda12a2 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,10 @@ jsoup changelog * Bugfix: when parsing HTML, could throw NPEs on some tags (isindex or table>input). <https://github.com/jhy/jsoup/issues/1404> + * Bugfix: in HttpConnection.Request, headers beginning with "sec-" (e.g. Sec-Fetch-Mode) were silently discarded by + the underlying Java HttpURLConnection. These are now settable correctly. + <https://github.com/jhy/jsoup/issues/1461> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index ff40431daf..b20e394197 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -533,6 +533,10 @@ public Map<String, String> cookies() { } public static class Request extends HttpConnection.Base<Connection.Request> implements Connection.Request { + static { + System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); + // make sure that we can send Sec-Fetch-Site headers etc. + } private Proxy proxy; // nullable private int timeoutMilliseconds; private int maxBodySizeBytes; diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 663ad448ac..6577c36ba0 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -139,6 +139,35 @@ public void doesPostMultipartWithoutInputstream() throws IOException { assertEquals("度一下", ihVal("百", doc)); } + @Test + public void canSendSecFetchHeaders() throws IOException { + // https://github.com/jhy/jsoup/issues/1461 + Document doc = Jsoup.connect(echoUrl) + .header("Random-Header-name", "hello") + .header("Sec-Fetch-Site", "cross-site") + .header("Sec-Fetch-Mode", "cors") + .get(); + + assertEquals("hello", ihVal("Random-Header-name", doc)); + assertEquals("cross-site", ihVal("Sec-Fetch-Site", doc)); + assertEquals("cors", ihVal("Sec-Fetch-Mode", doc)); + } + + @Test + public void secFetchHeadersSurviveRedirect() throws IOException { + Document doc = Jsoup + .connect(RedirectServlet.Url) + .data(RedirectServlet.LocationParam, echoUrl) + .header("Random-Header-name", "hello") + .header("Sec-Fetch-Site", "cross-site") + .header("Sec-Fetch-Mode", "cors") + .get(); + + assertEquals("hello", ihVal("Random-Header-name", doc)); + assertEquals("cross-site", ihVal("Sec-Fetch-Site", doc)); + assertEquals("cors", ihVal("Sec-Fetch-Mode", doc)); + } + @Test public void sendsRequestBodyJsonWithData() throws IOException { final String body = "{key:value}"; From 4df2bcd184dcf33f145c1ab3f3a11cd789546a14 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 22:53:33 +1100 Subject: [PATCH 458/774] Temp disable 16-ea until japicmp builds OK on it See https://github.com/siom79/siom79.github.io/issues/1 --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4f06da63b..66d5d21428 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,11 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time - java: [8, 11, 15, 16-ea] + java: [8, 11, 15] + # Temporarily removed 16-ea, as it started failing in the japicmp plugin phase. + # Working (with japacmp): https://github.com/jhy/jsoup/runs/1556442000?check_suite_focus=true + # Failing: https://github.com/jhy/jsoup/runs/1556669892?check_suite_focus=true + # Issue raised: https://github.com/siom79/siom79.github.io/issues/1 fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: From e8ae03d25c0bd50d433fbf493834484b068e5479 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 15 Dec 2020 22:56:25 +1100 Subject: [PATCH 459/774] Corrected issue link --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66d5d21428..6b618a49e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: # Temporarily removed 16-ea, as it started failing in the japicmp plugin phase. # Working (with japacmp): https://github.com/jhy/jsoup/runs/1556442000?check_suite_focus=true # Failing: https://github.com/jhy/jsoup/runs/1556669892?check_suite_focus=true - # Issue raised: https://github.com/siom79/siom79.github.io/issues/1 + # Issue raised: https://github.com/siom79/japicmp/issues/275 fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: From d45098eb5657e83880476fd1f7ff257f0ba6b59c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 12:53:45 +1100 Subject: [PATCH 460/774] Add initial nullability annotations (#1467) Using JSR-305, which IntelliJ and Kotlin and other checkers are compatible with to annotate nullability. Includes initial annotations for ReturnsNonnull and ParametersNonnull for Jsoup.class. Over time, the goal will be to add those to all packages, and only explicitly specify nullable returns / params / fields. --- CHANGES | 3 ++ pom.xml | 17 +++++++--- src/main/java/org/jsoup/Jsoup.java | 13 +++++--- .../internal/ReturnsAreNonnullByDefault.java | 20 ++++++++++++ .../org/jsoup/parser/HtmlTreeBuilder.java | 3 +- .../java/org/jsoup/parser/TreeBuilder.java | 4 +++ .../java/org/jsoup/parser/XmlTreeBuilder.java | 3 +- .../java/org/jsoup/helper/ValidateTest.java | 19 ++++++++++++ .../org/jsoup/parser/HtmlTreeBuilderTest.java | 31 ++++++++++++++++++- 9 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/jsoup/internal/ReturnsAreNonnullByDefault.java create mode 100644 src/test/java/org/jsoup/helper/ValidateTest.java diff --git a/CHANGES b/CHANGES index 491fda12a2..610a268267 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,9 @@ jsoup changelog * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. + * Build Improvement: added nullability annotations and initial settings. + <https://github.com/jhy/jsoup/pull/1467> + * Bugfix: when parsing HTML, could throw NPEs on some tags (isindex or table>input). <https://github.com/jhy/jsoup/issues/1404> diff --git a/pom.xml b/pom.xml index c7328e0519..7de140e3e1 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,10 @@ <url>https://jhy.io/</url> </organization> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + <build> <plugins> <plugin> @@ -311,7 +315,6 @@ <scope>test</scope> </dependency> - <dependency> <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> @@ -320,6 +323,14 @@ <scope>test</scope> </dependency> + <dependency> + <!-- javax.annotations.nonnull, with Apache 2 (not GPL) license. Not distributed. --> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>3.0.2</version> + <scope>compile</scope> + </dependency> + </dependencies> <dependencyManagement> @@ -327,10 +338,6 @@ </dependencies> </dependencyManagement> - <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - </properties> - <developers> <developer> <id>jhy</id> diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 5fe628614f..066978a60c 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -2,12 +2,15 @@ import org.jsoup.helper.DataUtil; import org.jsoup.helper.HttpConnection; +import org.jsoup.internal.ReturnsAreNonnullByDefault; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.jsoup.safety.Cleaner; import org.jsoup.safety.Safelist; import org.jsoup.safety.Whitelist; +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -17,6 +20,8 @@ The core public access point to the jsoup functionality. @author Jonathan Hedley */ + +@ParametersAreNonnullByDefault @ReturnsAreNonnullByDefault public class Jsoup { private Jsoup() {} @@ -85,7 +90,7 @@ public static Connection connect(String url) { @throws IOException if the file could not be found, or read, or if the charsetName is invalid. */ - public static Document parse(File in, String charsetName, String baseUri) throws IOException { + public static Document parse(File in, @Nullable String charsetName, String baseUri) throws IOException { return DataUtil.load(in, charsetName, baseUri); } @@ -100,7 +105,7 @@ public static Document parse(File in, String charsetName, String baseUri) throws @throws IOException if the file could not be found, or read, or if the charsetName is invalid. @see #parse(File, String, String) */ - public static Document parse(File in, String charsetName) throws IOException { + public static Document parse(File in, @Nullable String charsetName) throws IOException { return DataUtil.load(in, charsetName, in.getAbsolutePath()); } @@ -115,7 +120,7 @@ public static Document parse(File in, String charsetName) throws IOException { @throws IOException if the file could not be found, or read, or if the charsetName is invalid. */ - public static Document parse(InputStream in, String charsetName, String baseUri) throws IOException { + public static Document parse(InputStream in, @Nullable String charsetName, String baseUri) throws IOException { return DataUtil.load(in, charsetName, baseUri); } @@ -132,7 +137,7 @@ public static Document parse(InputStream in, String charsetName, String baseUri) @throws IOException if the file could not be found, or read, or if the charsetName is invalid. */ - public static Document parse(InputStream in, String charsetName, String baseUri, Parser parser) throws IOException { + public static Document parse(InputStream in, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { return DataUtil.load(in, charsetName, baseUri, parser); } diff --git a/src/main/java/org/jsoup/internal/ReturnsAreNonnullByDefault.java b/src/main/java/org/jsoup/internal/ReturnsAreNonnullByDefault.java new file mode 100644 index 0000000000..3f004a68a2 --- /dev/null +++ b/src/main/java/org/jsoup/internal/ReturnsAreNonnullByDefault.java @@ -0,0 +1,20 @@ +package org.jsoup.internal; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Nonnull +@TypeQualifierDefault(ElementType.METHOD) +@Retention(value = RetentionPolicy.RUNTIME) + +/** + Indicates return types are not nullable, unless otherwise specified by @Nullable. + @see javax.annotation.ParametersAreNonnullByDefault + */ +public @interface ReturnsAreNonnullByDefault { +} diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 39520ed5f7..cb398a2cb1 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -12,6 +12,7 @@ import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; +import javax.annotation.ParametersAreNonnullByDefault; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; @@ -60,7 +61,7 @@ ParseSettings defaultSettings() { return ParseSettings.htmlDefault; } - @Override + @Override @ParametersAreNonnullByDefault protected void initialiseParse(Reader input, String baseUri, Parser parser) { super.initialiseParse(input, baseUri, parser); diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 5292705c17..5c1ecfbfba 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -6,6 +6,7 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import javax.annotation.ParametersAreNonnullByDefault; import java.io.Reader; import java.util.ArrayList; import java.util.List; @@ -27,9 +28,11 @@ abstract class TreeBuilder { private Token.EndTag end = new Token.EndTag(); abstract ParseSettings defaultSettings(); + @ParametersAreNonnullByDefault protected void initialiseParse(Reader input, String baseUri, Parser parser) { Validate.notNull(input, "String input must not be null"); Validate.notNull(baseUri, "BaseURI must not be null"); + Validate.notNull(parser); doc = new Document(baseUri); doc.parser(parser); @@ -42,6 +45,7 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { this.baseUri = baseUri; } + @ParametersAreNonnullByDefault Document parse(Reader input, String baseUri, Parser parser) { initialiseParse(input, baseUri, parser); runParser(); diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index d68156dc38..1be8bf1e42 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -10,6 +10,7 @@ import org.jsoup.nodes.TextNode; import org.jsoup.nodes.XmlDeclaration; +import javax.annotation.ParametersAreNonnullByDefault; import java.io.Reader; import java.io.StringReader; import java.util.List; @@ -26,7 +27,7 @@ ParseSettings defaultSettings() { return ParseSettings.preserveCase; } - @Override + @Override @ParametersAreNonnullByDefault protected void initialiseParse(Reader input, String baseUri, Parser parser) { super.initialiseParse(input, baseUri, parser); stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) diff --git a/src/test/java/org/jsoup/helper/ValidateTest.java b/src/test/java/org/jsoup/helper/ValidateTest.java new file mode 100644 index 0000000000..138e532420 --- /dev/null +++ b/src/test/java/org/jsoup/helper/ValidateTest.java @@ -0,0 +1,19 @@ +package org.jsoup.helper; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ValidateTest { + + @Test + public void testNotNull() { + Validate.notNull("foo"); + boolean threw = false; + try { + Validate.notNull(null); + } catch (IllegalArgumentException e) { + threw = true; + } + Assertions.assertTrue(threw); + } +} diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java index 04630dc990..086a1c2eb6 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java @@ -3,9 +3,14 @@ import org.junit.jupiter.api.Test; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import java.io.Reader; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.Arrays; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.*; public class HtmlTreeBuilderTest { @Test @@ -26,4 +31,28 @@ public void ensureSearchArraysAreSorted() { assertArrayEquals(array, copy); } } + + @Test + public void nonnull() { + assertThrows(IllegalArgumentException.class, () -> { + HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder(); + treeBuilder.parse(null, null, null); // not sure how to test that these visual warnings actually appear! - test below checks for method annotation + } + ); // I'm not convinced that this lambda is easier to read than the old Junit 4 @Test(expected=IEA.class)... + } + + @Test public void nonnullAssertions() throws NoSuchMethodException { + Method parseMethod = TreeBuilder.class.getDeclaredMethod("parse", Reader.class, String.class, Parser.class); + assertNotNull(parseMethod); + Annotation[] declaredAnnotations = parseMethod.getDeclaredAnnotations(); + boolean seen = false; + for (Annotation annotation : declaredAnnotations) { + if (annotation.annotationType().isAssignableFrom(ParametersAreNonnullByDefault.class)) + seen = true; + } + + // would need to rework this if/when that annotation moves from the method to the class / package. + assertTrue(seen); + + } } From 43c35a792cf234c2c86616a81a4097c0d3ab693d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 13:10:15 +1100 Subject: [PATCH 461/774] Clean up sort test --- .../org/jsoup/parser/HtmlTreeBuilder.java | 2 +- .../parser/HtmlTreeBuilderStateTest.java | 6 +++--- .../org/jsoup/parser/HtmlTreeBuilderTest.java | 19 ++++--------------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index cb398a2cb1..3c847c3ead 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -24,7 +24,7 @@ * HTML Tree Builder; creates a DOM from Tokens. */ public class HtmlTreeBuilder extends TreeBuilder { - // tag searches. must be sorted, used in inSorted. MUST update HtmlTreeBuilderTest if more arrays are added. + // tag searches. must be sorted, used in inSorted. HtmlTreeBuilderTest validates they're sorted. static final String[] TagsSearchInScope = new String[]{"applet", "caption", "html", "marquee", "object", "table", "td", "th"}; static final String[] TagSearchList = new String[]{"ol", "ul"}; static final String[] TagSearchButton = new String[]{"button"}; diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 97bd5287a5..ea234d8439 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -13,9 +13,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class HtmlTreeBuilderStateTest { - static List<Object[]> findArrays() { + static List<Object[]> findConstantArrays(Class aClass) { ArrayList<Object[]> array = new ArrayList<>(); - Field[] fields = Constants.class.getDeclaredFields(); + Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) && field.getType().isArray()) { @@ -40,7 +40,7 @@ static void ensureSorted(List<Object[]> constants) { @Test public void ensureArraysAreSorted() { - List<Object[]> constants = findArrays(); + List<Object[]> constants = findConstantArrays(Constants.class); ensureSorted(constants); assertEquals(38, constants.size()); } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java index 086a1c2eb6..133af5fb1a 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java @@ -9,27 +9,16 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; public class HtmlTreeBuilderTest { @Test public void ensureSearchArraysAreSorted() { - String[][] arrays = { - HtmlTreeBuilder.TagsSearchInScope, - HtmlTreeBuilder.TagSearchList, - HtmlTreeBuilder.TagSearchButton, - HtmlTreeBuilder.TagSearchTableScope, - HtmlTreeBuilder.TagSearchSelectScope, - HtmlTreeBuilder.TagSearchEndTags, - HtmlTreeBuilder.TagSearchSpecial - }; - - for (String[] array : arrays) { - String[] copy = Arrays.copyOf(array, array.length); - Arrays.sort(array); - assertArrayEquals(array, copy); - } + List<Object[]> constants = HtmlTreeBuilderStateTest.findConstantArrays(HtmlTreeBuilder.class); + HtmlTreeBuilderStateTest.ensureSorted(constants); + assertEquals(7, constants.size()); } @Test From ee05221daeba68e00fb452341b13dd6520334630 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 13:26:05 +1100 Subject: [PATCH 462/774] Minor code tweaks --- src/main/java/org/jsoup/helper/HttpConnection.java | 1 - src/main/java/org/jsoup/nodes/FormElement.java | 2 +- src/main/java/org/jsoup/parser/CharacterReader.java | 6 +++--- src/main/java/org/jsoup/parser/TokenQueue.java | 6 +++--- src/main/java/org/jsoup/parser/Tokeniser.java | 3 +-- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index b20e394197..bf28371e13 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -31,7 +31,6 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index 7d631fbbd0..e9e8302e9a 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -62,7 +62,7 @@ protected void removeChild(Node out) { public Connection submit() { String action = hasAttr("action") ? absUrl("action") : baseUri(); Validate.notEmpty(action, "Could not determine a form action URL for submit. Ensure you set a base URI when parsing."); - Connection.Method method = attr("method").toUpperCase().equals("POST") ? + Connection.Method method = attr("method").equalsIgnoreCase("POST") ? Connection.Method.POST : Connection.Method.GET; return Jsoup.connect(action) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index d4f0d2b859..94710fd0e6 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -33,7 +33,7 @@ public CharacterReader(Reader input, int sz) { Validate.notNull(input); Validate.isTrue(input.markSupported()); reader = input; - charBuf = new char[sz > maxBufferLen ? maxBufferLen : sz]; + charBuf = new char[Math.min(sz, maxBufferLen)]; bufferUp(); } @@ -93,7 +93,7 @@ private void bufferUp() { bufPos = offset; if (bufMark != -1) bufMark = 0; - bufSplitPoint = bufLength > readAheadLimit ? readAheadLimit : bufLength; + bufSplitPoint = Math.min(bufLength, readAheadLimit); } } catch (IOException e) { throw new UncheckedIOException(e); @@ -327,7 +327,7 @@ String consumeAttributeQuoted(final boolean single) { case '\'': if (single) break OUTER; case '"': - if (!single) break OUTER;; + if (!single) break OUTER; default: pos++; } diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 8b4cbb2e42..f269051708 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -268,7 +268,7 @@ public String chompBalanced(char open, char close) { do { if (isEmpty()) break; char c = consume(); - if (last == 0 || last != ESC) { + if (last != ESC) { if (c == '\'' && c != open && !inDoubleQuote) inSingleQuote = !inSingleQuote; else if (c == '"' && c != open && !inSingleQuote) @@ -306,7 +306,7 @@ public static String unescape(String in) { char last = 0; for (char c : in.toCharArray()) { if (c == ESC) { - if (last != 0 && last == ESC) + if (last == ESC) out.append(c); } else @@ -396,7 +396,7 @@ public String consumeAttributeKey() { @return remained of queue. */ public String remainder() { - final String remainder = queue.substring(pos, queue.length()); + final String remainder = queue.substring(pos); pos = queue.length(); return remainder; } diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index db053ea8f2..0f08cae2a5 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -183,7 +183,6 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) { characterReferenceError("character outside of valid range"); codeRef[0] = replacementChar; - return codeRef; } else { // fix illegal unicode characters to match browser behavior if (charval >= win1252ExtensionsStart && charval < win1252ExtensionsStart + win1252Extensions.length) { @@ -194,8 +193,8 @@ int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean in // todo: implement number replacement table // todo: check for extra illegal unicode points as parse errors codeRef[0] = charval; - return codeRef; } + return codeRef; } else { // named // get as many letters as possible, and look for matching entities. String nameRef = reader.consumeLetterThenDigitSequence(); From a7c07d968dd76c24cea805363f00550a84e15533 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 14:07:30 +1100 Subject: [PATCH 463/774] Fix issue in addNodes where could incorrectly reparent --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Node.java | 16 +++++++------ .../java/org/jsoup/nodes/ElementTest.java | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 610a268267..c0d95e1b67 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,9 @@ jsoup changelog the underlying Java HttpURLConnection. These are now settable correctly. <https://github.com/jhy/jsoup/issues/1461> + * Bugfix: when adding child Nodes to a Node, could incorrectly reparent all nodes if the first parent had the same + length of children as the incoming node list. + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 5829631358..8602a48cb0 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -477,14 +477,16 @@ protected void addChildren(int index, Node... children) { break; } } - firstParent.empty(); - nodes.addAll(index, Arrays.asList(children)); - i = children.length; - while (i-- > 0) { - children[i].parentNode = this; + if (sameList) { // moving, so OK to empty firstParent and short-circuit + firstParent.empty(); + nodes.addAll(index, Arrays.asList(children)); + i = children.length; + while (i-- > 0) { + children[i].parentNode = this; + } + reindexChildren(index); + return; } - reindexChildren(index); - return; } Validate.noNullElements(children); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 6cc67e8c12..78fe09bd0e 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1590,6 +1590,30 @@ public void testReparentSeperateNodes() { assertEquals("<p>Four</p><p>Three</p><p>Four</p><div><p>One</p><p>Two</p></div><p>Three</p>", TextUtil.stripNewlines(doc.body().html())); } + @Test + public void testNotActuallyAReparent() { + // prep + String html = "<div>"; + Document doc = Jsoup.parse(html); + Element div = doc.selectFirst("div"); + Element new1 = new Element("p").text("One"); + Element new2 = new Element("p").text("Two"); + div.addChildren(new1, new2); + + assertEquals("<div><p>One</p><p>Two</p></div>", TextUtil.stripNewlines(div.outerHtml())); + + // and the issue setup: + Element new3 = new Element("p").text("Three"); + Element wrap = new Element("nav"); + wrap.addChildren(0, new1, new3); + + assertEquals("<nav><p>One</p><p>Three</p></nav>", TextUtil.stripNewlines(wrap.outerHtml())); + div.addChildren(wrap); + // now should be that One moved into wrap, leaving Two in div. + + assertEquals("<div><p>Two</p><nav><p>One</p><p>Three</p></nav></div>", TextUtil.stripNewlines(div.outerHtml())); + } + @Test public void testChildSizeWithMixedContent() { Document doc = Jsoup.parse("<table><tbody>\n<tr>\n<td>15:00</td>\n<td>sport</td>\n</tr>\n</tbody></table>"); From 457aea0619e1dad63e03a8b7b7a61d030d5ec946 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 14:48:11 +1100 Subject: [PATCH 464/774] Javadoc tweak --- src/main/java/org/jsoup/helper/package-info.java | 5 +++++ src/main/javadoc/overview.html | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/jsoup/helper/package-info.java diff --git a/src/main/java/org/jsoup/helper/package-info.java b/src/main/java/org/jsoup/helper/package-info.java new file mode 100644 index 0000000000..592fc022e5 --- /dev/null +++ b/src/main/java/org/jsoup/helper/package-info.java @@ -0,0 +1,5 @@ +/** + Package containing classes supporting the core jsoup code. + */ + +package org.jsoup.helper; diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 0fa25e11b1..597cdcb47d 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> <title>jsoup Javadoc overview</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> @@ -23,7 +23,7 @@ <h1>jsoup: Java HTML parser that makes sense of real-world HTML soup.</h1> <p>jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; jsoup will create a sensible parse tree.</p> -<p>See <a href="https://jsoup.org/"><b>jsoup.org</b></a> for downloads, documentation, and examples...</p> +<p>See <a href="https://jsoup.org/"><b>jsoup.org</b></a> for downloads, documentation, and examples.</p> @author <a href="https://jonathanhedley.com/">Jonathan Hedley</a> From 4dc96506ca76074e59341cf1e7ef46450baf777a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 15:07:00 +1100 Subject: [PATCH 465/774] Text javadoc clarification --- src/main/java/org/jsoup/nodes/Element.java | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 8f763cbc89..d43612e7b1 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1126,14 +1126,19 @@ public Elements getAllElements() { } /** - * Gets the combined text of this element and all its children. Whitespace is normalized and trimmed. - * <p> - * For example, given HTML {@code <p>Hello <b>there</b> now! </p>}, {@code p.text()} returns {@code "Hello there now!"} - * - * @return unencoded, normalized text, or empty string if none. - * @see #wholeText() if you don't want the text to be normalized. - * @see #ownText() - * @see #textNodes() + Gets the <b>normalized, combined text</b> of this element and all its children. Whitespace is normalized and + trimmed. + <p> + For example, given HTML {@code <p>Hello <b>there</b> now! </p>}, {@code p.text()} returns {@code "Hello there + now!"} + <p> + If you do not want normalized text, use {@link #wholeText()}. If you want just the text of this node (and not + children), use {@link #ownText()} + + @return unencoded, normalized text, or empty string if none. + @see #wholeText() + @see #ownText() + @see #textNodes() */ public String text() { final StringBuilder accum = StringUtil.borrowBuilder(); @@ -1190,7 +1195,7 @@ public void tail(Node node, int depth) { } /** - * Gets the text owned by this element only; does not get the combined text of all children. + * Gets the (normalized) text owned by this element only; does not get the combined text of all children. * <p> * For example, given HTML {@code <p>Hello <b>there</b> now!</p>}, {@code p.ownText()} returns {@code "Hello now!"}, * whereas {@code p.text()} returns {@code "Hello there now!"}. @@ -1282,7 +1287,7 @@ public boolean hasText() { /** * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. Note that data is NOT the - * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@link #data()} + * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@code data()} * for the contents of scripts, comments, CSS styles, etc. * * @return the data, or empty string if none From 5727d18a04df73982ce8e6d77e965cb768cfee7d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 15:17:04 +1100 Subject: [PATCH 466/774] Added Element#id(String) setter Symmetrical to Element#id() getter --- CHANGES | 2 ++ src/main/java/org/jsoup/nodes/Element.java | 11 +++++++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 14 ++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGES b/CHANGES index c0d95e1b67..66eaa2031f 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,8 @@ jsoup changelog * Improvement: better parsing performance when under high thread concurrency <https://github.com/jhy/jsoup/pull/1402> + * Improvement: added Element#id(String) ID attribute setter. + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index d43612e7b1..b26a94b74c 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -193,6 +193,17 @@ public String id() { return hasAttributes() ? attributes.getIgnoreCase("id") :""; } + /** + Set the {@code id} attribute of this element. + @param id the ID value to use + @return this Element, for chaining + */ + public Element id(String id) { + Validate.notNull(id); + attr("id", id); + return this; + } + /** * Set an attribute value on this element. If this element already has an attribute with the * key, its value is updated; otherwise, a new attribute is added. diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 78fe09bd0e..3174dede4f 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -19,6 +19,19 @@ public class ElementTest { private String reference = "<div id=div1><p>Hello</p><p>Another <b>element</b></p><div id=div2><img src=foo.png></div></div>"; + @Test public void testId() { + Document doc = Jsoup.parse("<div id=Foo>"); + Element el = doc.selectFirst("div"); + assertEquals("Foo", el.id()); + } + + @Test public void testSetId() { + Document doc = Jsoup.parse("<div id=Boo>"); + Element el = doc.selectFirst("div"); + el.id("Foo"); + assertEquals("Foo", el.id()); + } + @Test public void getElementsByTagName() { Document doc = Jsoup.parse(reference); List<Element> divs = doc.getElementsByTag("div"); @@ -1612,6 +1625,7 @@ public void testNotActuallyAReparent() { // now should be that One moved into wrap, leaving Two in div. assertEquals("<div><p>Two</p><nav><p>One</p><p>Three</p></nav></div>", TextUtil.stripNewlines(div.outerHtml())); + assertEquals("<div><p>Two</p><nav><p>One</p><p>Three</p></nav></div>", TextUtil.stripNewlines(div.outerHtml())); } @Test From 77fcaf4dcdc8fd185e2f5d8eb6aa9c425a38bc9b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 20 Dec 2020 18:08:59 +1100 Subject: [PATCH 467/774] Added nullability annotations to nodes package And fixed some NPEs --- CHANGES | 5 ++ .../internal/FieldsAreNonnullByDefault.java | 20 ++++++ .../org/jsoup/internal/NonnullByDefault.java | 20 ++++++ src/main/java/org/jsoup/nodes/Attribute.java | 15 ++--- src/main/java/org/jsoup/nodes/Attributes.java | 8 ++- src/main/java/org/jsoup/nodes/Comment.java | 3 +- src/main/java/org/jsoup/nodes/Document.java | 58 ++++++++--------- src/main/java/org/jsoup/nodes/Element.java | 55 +++++++++------- src/main/java/org/jsoup/nodes/Entities.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 61 +++++++++++------- .../java/org/jsoup/nodes/package-info.java | 5 +- .../org/jsoup/parser/HtmlTreeBuilder.java | 3 +- .../java/org/jsoup/nodes/ElementTest.java | 63 ++++++++++++++++++- 13 files changed, 228 insertions(+), 90 deletions(-) create mode 100644 src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java create mode 100644 src/main/java/org/jsoup/internal/NonnullByDefault.java diff --git a/CHANGES b/CHANGES index 66eaa2031f..c54d26eaf6 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,11 @@ jsoup changelog * Bugfix: when adding child Nodes to a Node, could incorrectly reparent all nodes if the first parent had the same length of children as the incoming node list. + * Bugfix: when wrapping an orphaned element, would throw an NPE. + + * Bugfix: when wrapping an element with HTML that included multiple sibling elements, those siblings were incorrectly + added as children of the wrapper instead of siblings. + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java b/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java new file mode 100644 index 0000000000..f501ea3975 --- /dev/null +++ b/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java @@ -0,0 +1,20 @@ +package org.jsoup.internal; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Nonnull +@TypeQualifierDefault(ElementType.FIELD) +@Retention(value = RetentionPolicy.RUNTIME) + +/** + Indicates that fields types are not nullable, unless otherwise specified by @Nullable. + @see javax.annotation.ParametersAreNonnullByDefault + */ +public @interface FieldsAreNonnullByDefault { +} diff --git a/src/main/java/org/jsoup/internal/NonnullByDefault.java b/src/main/java/org/jsoup/internal/NonnullByDefault.java new file mode 100644 index 0000000000..e3497045b8 --- /dev/null +++ b/src/main/java/org/jsoup/internal/NonnullByDefault.java @@ -0,0 +1,20 @@ +package org.jsoup.internal; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Nonnull +@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) +@Retention(value = RetentionPolicy.RUNTIME) + +/** + Indicates that all components (methods, returns, fields) are not nullable, unless otherwise specified by @Nullable. + @see javax.annotation.ParametersAreNonnullByDefault + */ +public @interface NonnullByDefault { +} diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index ae74971fa1..3bbc24a84e 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -4,6 +4,7 @@ import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import javax.annotation.Nullable; import java.io.IOException; import java.util.Arrays; import java.util.Map; @@ -20,8 +21,8 @@ public class Attribute implements Map.Entry<String, String>, Cloneable { }; private String key; - private String val; - Attributes parent; // used to update the holding Attributes when the key / value is changed via this interface + @Nullable private String val; + @Nullable Attributes parent; // used to update the holding Attributes when the key / value is changed via this interface /** * Create a new attribute from unencoded (raw) key and value. @@ -29,17 +30,17 @@ public class Attribute implements Map.Entry<String, String>, Cloneable { * @param value attribute value (may be null) * @see #createFromEncoded */ - public Attribute(String key, String value) { + public Attribute(String key, @Nullable String value) { this(key, value, null); } /** * Create a new attribute from unencoded (raw) key and value. * @param key attribute key; case is preserved. - * @param val attribute value + * @param val attribute value (may be null) * @param parent the containing Attributes (this Attribute is not automatically added to said Attributes) * @see #createFromEncoded*/ - public Attribute(String key, String val, Attributes parent) { + public Attribute(String key, @Nullable String val, @Nullable Attributes parent) { Validate.notNull(key); key = key.trim(); Validate.notEmpty(key); // trimming could potentially make empty, so validate here @@ -119,7 +120,7 @@ public String html() { return StringUtil.releaseBuilder(sb); } - protected static void html(String key, String val, Appendable accum, Document.OutputSettings out) throws IOException { + protected static void html(String key, @Nullable String val, Appendable accum, Document.OutputSettings out) throws IOException { accum.append(key); if (!shouldCollapseAttribute(key, val, out)) { accum.append("=\""); @@ -170,7 +171,7 @@ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { return shouldCollapseAttribute(key, val, out); } - protected static boolean shouldCollapseAttribute(final String key, final String val, final Document.OutputSettings out) { + protected static boolean shouldCollapseAttribute(final String key, @Nullable final String val, final Document.OutputSettings out) { return ( out.syntax() == Document.OutputSettings.Syntax.html && (val == null || ("".equals(val) || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 0f22f36cbf..1d6e6904e5 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -5,6 +5,7 @@ import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; +import javax.annotation.Nullable; import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; @@ -82,7 +83,7 @@ private int indexOfKeyIgnoreCase(String key) { } // we track boolean attributes as null in values - they're just keys. so returns empty for consumers - static String checkNotNull(String val) { + static String checkNotNull(@Nullable String val) { return val == null ? EmptyString : val; } @@ -111,7 +112,7 @@ public String getIgnoreCase(String key) { * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ - public Attributes add(String key, String value) { + public Attributes add(String key, @Nullable String value) { checkCapacity(size + 1); keys[size] = key; vals[size] = value; @@ -135,7 +136,7 @@ public Attributes put(String key, String value) { return this; } - void putIgnoreCase(String key, String value) { + void putIgnoreCase(String key, @Nullable String value) { int i = indexOfKeyIgnoreCase(key); if (i != NotFound) { vals[i] = value; @@ -173,6 +174,7 @@ public Attributes put(Attribute attribute) { } // removes and shifts up + @SuppressWarnings("AssignmentToNull") private void remove(int index) { Validate.isFalse(index >= size); int shifted = size - index - 1; diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index d80c500637..c966d77d6f 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -3,6 +3,7 @@ import org.jsoup.Jsoup; import org.jsoup.parser.Parser; +import javax.annotation.Nullable; import java.io.IOException; /** @@ -69,7 +70,7 @@ public boolean isXmlDeclaration() { * Attempt to cast this comment to an XML Declaration note. * @return an XML declaration if it could be parsed as one, null otherwise. */ - public XmlDeclaration asXmlDeclaration() { + public @Nullable XmlDeclaration asXmlDeclaration() { String data = getData(); Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri(), Parser.xmlParser()); XmlDeclaration decl = null; diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index c39a75087e..8a57058835 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -1,13 +1,14 @@ package org.jsoup.nodes; import org.jsoup.helper.DataUtil; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; +import javax.annotation.Nullable; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.ArrayList; @@ -33,6 +34,7 @@ public class Document extends Element { public Document(String baseUri) { super(Tag.valueOf("#root", ParseSettings.htmlDefault), baseUri); this.location = baseUri; + this.parser = Parser.htmlParser(); // default, but overridable } /** @@ -65,7 +67,7 @@ public String location() { * Returns this Document's doctype. * @return document type, or null if not set */ - public DocumentType documentType() { + public @Nullable DocumentType documentType() { for (Node node : childNodes) { if (node instanceof DocumentType) return (DocumentType) node; @@ -80,16 +82,18 @@ else if (!(node instanceof LeafNode)) // scans forward across comments, text, pr Accessor to the document's {@code head} element. @return {@code head} */ - public Element head() { - return findFirstElementByTagName("head", this); + public @Nullable Element head() { + // todo - we practically enforce this - move to nonnull + return findFirstElementByTagName("head"); } /** Accessor to the document's {@code body} element. @return {@code body} */ - public Element body() { - return findFirstElementByTagName("body", this); + public @Nullable Element body() { + // todo - we practically enforce this - move to nonnull + return findFirstElementByTagName("body"); } /** @@ -132,7 +136,7 @@ public Element createElement(String tagName) { @return this document after normalisation */ public Document normalise() { - Element htmlEl = findFirstElementByTagName("html", this); + Element htmlEl = findFirstElementByTagName("html"); if (htmlEl == null) htmlEl = appendElement("html"); if (head() == null) @@ -189,24 +193,24 @@ private void normaliseStructure(String tag, Element htmlEl) { master.appendChild(dupe); } // ensure parented by <html> - if (!master.parent().equals(htmlEl)) { + if (master.parent() != null && !master.parent().equals(htmlEl)) { htmlEl.appendChild(master); // includes remove() } } - // fast method to get first by tag name, used for html, head, body finders - private Element findFirstElementByTagName(String tag, Node node) { - if (node.nodeName().equals(tag)) - return (Element) node; + // fast method to get first by tag name, used for html, head, body finders - no recursive descent + private @Nullable Element findFirstElementByTagName(String tag) { + Element root = child(0); // the HTML element - Document sits above + if (root.nodeName().equals(tag)) + return this; else { - int size = node.childNodeSize(); + int size = root.childNodeSize(); for (int i = 0; i < size; i++) { - Element found = findFirstElementByTagName(tag, node.childNode(i)); - if (found != null) - return found; + if (root.childNode(i).nodeName().equals(tag)) + return (Element) root.childNode(i); } } - return null; + return null; // todo - make this blow up in most cases - how to handle frameset (no body)?) } @Override @@ -348,30 +352,22 @@ private void ensureMetaCharsetElement() { select("meta[name=charset]").remove(); } else if (syntax == OutputSettings.Syntax.xml) { Node node = childNodes().get(0); - if (node instanceof XmlDeclaration) { XmlDeclaration decl = (XmlDeclaration) node; - if (decl.name().equals("xml")) { decl.attr("encoding", charset().displayName()); - - final String version = decl.attr("version"); - - if (version != null) { + if (decl.hasAttr("version")) decl.attr("version", "1.0"); - } } else { decl = new XmlDeclaration("xml", false); decl.attr("version", "1.0"); decl.attr("encoding", charset().displayName()); - prependChild(decl); } } else { XmlDeclaration decl = new XmlDeclaration("xml", false); decl.attr("version", "1.0"); decl.attr("encoding", charset().displayName()); - prependChild(decl); } } @@ -389,18 +385,16 @@ public static class OutputSettings implements Cloneable { public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; - private Charset charset; - private ThreadLocal<CharsetEncoder> encoderThreadLocal = new ThreadLocal<>(); // initialized by start of OuterHtmlVisitor - Entities.CoreCharset coreCharset; // fast encoders for ascii and utf8 + private Charset charset = DataUtil.UTF_8; + private final ThreadLocal<CharsetEncoder> encoderThreadLocal = new ThreadLocal<>(); // initialized by start of OuterHtmlVisitor + @Nullable Entities.CoreCharset coreCharset; // fast encoders for ascii and utf8 private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; private Syntax syntax = Syntax.html; - public OutputSettings() { - charset(DataUtil.UTF_8); - } + public OutputSettings() {} /** * Get the document's current HTML escape mode: <code>base</code>, which provides a limited set of named HTML diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index b26a94b74c..052b6352a0 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -2,6 +2,9 @@ import org.jsoup.helper.ChangeNotifyingArrayList; import org.jsoup.helper.Validate; +import org.jsoup.internal.FieldsAreNonnullByDefault; +import org.jsoup.internal.NonnullByDefault; +import org.jsoup.internal.ReturnsAreNonnullByDefault; import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Tag; @@ -14,6 +17,8 @@ import org.jsoup.select.QueryParser; import org.jsoup.select.Selector; +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -37,14 +42,15 @@ * * @author Jonathan Hedley, jonathan@hedley.net */ +@NonnullByDefault public class Element extends Node { private static final List<Node> EMPTY_NODES = Collections.emptyList(); private static final Pattern classSplit = Pattern.compile("\\s+"); private static final String baseUriKey = Attributes.internalKey("baseUri"); private Tag tag; - private WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children + private @Nullable WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children List<Node> childNodes; - private Attributes attributes; + private @Nullable Attributes attributes; // field is nullable but all methods for attributes are non null /** * Create a new, standalone element. @@ -63,7 +69,7 @@ public Element(String tag) { * @see #appendChild(Node) * @see #appendElement(String) */ - public Element(Tag tag, String baseUri, Attributes attributes) { + public Element(Tag tag, @Nullable String baseUri, @Nullable Attributes attributes) { Validate.notNull(tag); childNodes = EMPTY_NODES; this.attributes = attributes; @@ -97,7 +103,7 @@ protected boolean hasAttributes() { @Override public Attributes attributes() { - if (!hasAttributes()) + if (attributes == null) // not using hasAttributes, as doesn't clear warning attributes = new Attributes(); return attributes; } @@ -110,7 +116,7 @@ public String baseUri() { private static String searchUpForAttribute(final Element start, final String key) { Element el = start; while (el != null) { - if (el.hasAttributes() && el.attributes.hasKey(key)) + if (el.attributes != null && el.attributes.hasKey(key)) return el.attributes.get(key); el = el.parent(); } @@ -190,7 +196,7 @@ public boolean isBlock() { * @return The id attribute, if present, or an empty string if not. */ public String id() { - return hasAttributes() ? attributes.getIgnoreCase("id") :""; + return attributes != null ? attributes.getIgnoreCase("id") :""; } /** @@ -470,7 +476,7 @@ public boolean is(Evaluator evaluator) { * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not * found. */ - public Element closest(String cssQuery) { + public @Nullable Element closest(String cssQuery) { return closest(QueryParser.parse(cssQuery)); } @@ -481,7 +487,7 @@ public Element closest(String cssQuery) { * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not * found. */ - public Element closest(Evaluator evaluator) { + public @Nullable Element closest(Evaluator evaluator) { Validate.notNull(evaluator); Element el = this; final Element root = root(); @@ -779,7 +785,7 @@ public Elements siblingElements() { * @return the next element, or null if there is no next element * @see #previousElementSibling() */ - public Element nextElementSibling() { + public @Nullable Element nextElementSibling() { if (parentNode == null) return null; List<Element> siblings = parent().childElementsList(); int index = indexInList(this, siblings); @@ -803,7 +809,7 @@ public Elements nextElementSiblings() { * @return the previous element, or null if there is no previous element * @see #nextElementSibling() */ - public Element previousElementSibling() { + public @Nullable Element previousElementSibling() { if (parentNode == null) return null; List<Element> siblings = parent().childElementsList(); int index = indexInList(this, siblings); @@ -831,13 +837,15 @@ private Elements nextElementSiblings(boolean next) { } /** - * Gets the first element sibling of this element. + * Gets the first Element sibling of this element. That may be this element. * @return the first sibling that is an element (aka the parent's first element child) */ public Element firstElementSibling() { - // todo: should firstSibling() exclude this? - List<Element> siblings = parent().childElementsList(); - return siblings.size() > 1 ? siblings.get(0) : null; + if (parent() != null) { + List<Element> siblings = parent().childElementsList(); + return siblings.size() > 1 ? siblings.get(0) : this; + } else + return this; // orphan is its own first sibling } /** @@ -851,12 +859,15 @@ public int elementSiblingIndex() { } /** - * Gets the last element sibling of this element + * Gets the last element sibling of this element. That may be this element. * @return the last sibling that is an element (aka the parent's last element child) */ public Element lastElementSibling() { - List<Element> siblings = parent().childElementsList(); - return siblings.size() > 1 ? siblings.get(siblings.size() - 1) : null; + if (parent() != null) { + List<Element> siblings = parent().childElementsList(); + return siblings.size() > 1 ? siblings.get(siblings.size() - 1) : this; + } else + return this; } private static <E extends Element> int indexInList(Element search, List<E> elements) { @@ -891,7 +902,7 @@ public Elements getElementsByTag(String tagName) { * @param id The ID to search for. * @return The first matching element by ID, starting with this element, or null if none found. */ - public Element getElementById(String id) { + public @Nullable Element getElementById(String id) { Validate.notEmpty(id); Elements elements = Collector.collect(new Evaluator.Id(id), this); @@ -1247,7 +1258,7 @@ private static void appendWhitespaceIfBr(Element element, StringBuilder accum) { accum.append(" "); } - static boolean preserveWhitespace(Node node) { + static boolean preserveWhitespace(@Nullable Node node) { // looks only at this element and five levels up, to prevent recursion & needless stack searches if (node instanceof Element) { Element el = (Element) node; @@ -1374,7 +1385,7 @@ public Element classNames(Set<String> classNames) { */ // performance sensitive public boolean hasClass(String className) { - if (!hasAttributes()) + if (attributes == null) return false; final String classAttr = attributes.getIgnoreCase("class"); @@ -1571,7 +1582,7 @@ public Element shallowClone() { } @Override - protected Element doClone(Node parent) { + protected Element doClone(@Nullable Node parent) { Element clone = (Element) super.doClone(parent); clone.attributes = attributes != null ? attributes.clone() : null; clone.childNodes = new NodeList(clone, childNodes.size()); @@ -1632,7 +1643,7 @@ private boolean isFormatAsBlock(Document.OutputSettings out) { private boolean isInlineable(Document.OutputSettings out) { return tag().isInline() && !tag().isEmpty() - && parent().isBlock() + && (parent() == null || parent().isBlock()) && previousSibling() != null && !out.outline(); } diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 1e5345914f..7c0c15b66f 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -45,7 +45,7 @@ public enum EscapeMode { private int[] codeVals; // limitation is the few references with multiple characters; those go into multipoints. // table of codepoints to named entities. - private int[] codeKeys; // we don' support multicodepoints to single named value currently + private int[] codeKeys; // we don't support multicodepoints to single named value currently private String[] nameVals; EscapeMode(String file, int size) { diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 8602a48cb0..b1e058888d 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -7,6 +7,7 @@ import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; +import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -21,7 +22,7 @@ @author Jonathan Hedley, jonathan@hedley.net */ public abstract class Node implements Cloneable { static final String EmptyString = ""; - Node parentNode; + @Nullable Node parentNode; // Nodes don't always have parents int siblingIndex; /** @@ -41,6 +42,11 @@ Get the node name of this node. Use for debugging purposes and not logic switchi */ protected abstract boolean hasAttributes(); + /** + Checks if this node has a parent. Nodes won't have parents if e.g. they are newly created and not added as a child + to an exsiting node, or if they are a {@link #shallowClone()}. In such cases, {@link #parent()} will return {@code null}. + @return if this node has a parent. + */ public boolean hasParent() { return parentNode != null; } @@ -240,8 +246,9 @@ protected Node[] childNodesAsArray() { /** Gets this node's parent node. @return parent node; or null if no parent. + @see #hasParent() */ - public Node parent() { + public @Nullable Node parent() { return parentNode; } @@ -249,7 +256,7 @@ public Node parent() { Gets this node's parent node. Not overridable by extending classes, so useful if you really just need the Node type. @return parent node; or null if no parent. */ - public final Node parentNode() { + public @Nullable final Node parentNode() { return parentNode; } @@ -268,7 +275,7 @@ public Node root() { * Gets the Document associated with this Node. * @return the Document associated with this Node, or null if there is no such Document. */ - public Document ownerDocument() { + public @Nullable Document ownerDocument() { Node root = root(); return (root instanceof Document) ? (Document) root : null; } @@ -342,30 +349,42 @@ private void addSiblingHtml(int index, String html) { /** Wrap the supplied HTML around this node. - @param html HTML to wrap around this element, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep. + + @param html HTML to wrap around this node, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep. If + the input HTML does not parse to a result starting with an Element, this will be a no-op. @return this node, for chaining. */ public Node wrap(String html) { Validate.notEmpty(html); - Element context = parent() instanceof Element ? (Element) parent() : null; + // Parse context - this if element or parent if element else null + Element context = + this instanceof Element ? (Element) this : + parentNode != null && parentNode instanceof Element ? (Element) parentNode : + null; List<Node> wrapChildren = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); Node wrapNode = wrapChildren.get(0); if (!(wrapNode instanceof Element)) // nothing to wrap with; noop - return null; + return this; Element wrap = (Element) wrapNode; Element deepest = getDeepChild(wrap); - parentNode.replaceChild(this, wrap); - deepest.addChildren(this); + if (parentNode != null) + parentNode.replaceChild(this, wrap); + deepest.addChildren(this); // side effect of tricking wrapChildren to lose first // remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder if (wrapChildren.size() > 0) { //noinspection ForLoopReplaceableByForEach (beacause it allocates an Iterator which is wasteful here) for (int i = 0; i < wrapChildren.size(); i++) { Node remainder = wrapChildren.get(i); - remainder.parentNode.removeChild(remainder); - wrap.appendChild(remainder); + // if no parent, this could be the wrap node, so skip + if (wrap == remainder) + continue; + + if (remainder.parentNode != null) + remainder.parentNode.removeChild(remainder); + wrap.after(remainder); } } return this; @@ -382,11 +401,11 @@ public Node wrap(String html) { * <p>{@code <div>One Two <b>Three</b></div>}</p> * and the {@code "Two "} {@link TextNode} being returned. * - * @return the first child of this node, after the node has been unwrapped. Null if the node had no children. + * @return the first child of this node, after the node has been unwrapped. @{code Null} if the node had no children. * @see #remove() * @see #wrap(String) */ - public Node unwrap() { + public @Nullable Node unwrap() { Validate.notNull(parentNode); final List<Node> childNodes = ensureChildNodes(); Node firstChild = childNodes.size() > 0 ? childNodes.get(0) : null; @@ -528,9 +547,9 @@ public List<Node> siblingNodes() { /** Get this node's next sibling. - @return next sibling, or null if this is the last sibling + @return next sibling, or @{code null} if this is the last sibling */ - public Node nextSibling() { + public @Nullable Node nextSibling() { if (parentNode == null) return null; // root @@ -544,9 +563,9 @@ public Node nextSibling() { /** Get this node's previous sibling. - @return the previous sibling, or null if this is the first sibling + @return the previous sibling, or @{code null} if this is the first sibling */ - public Node previousSibling() { + public @Nullable Node previousSibling() { if (parentNode == null) return null; // root @@ -659,7 +678,7 @@ public boolean equals(Object o) { * @param o other object to compare to * @return true if the content of this node is the same as the other */ - public boolean hasSameValue(Object o) { + public boolean hasSameValue(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -712,7 +731,7 @@ public Node shallowClone() { * Return a clone of the node using the given parent (which can be null). * Not a deep copy of children. */ - protected Node doClone(Node parent) { + protected Node doClone(@Nullable Node parent) { Node clone; try { @@ -728,8 +747,8 @@ protected Node doClone(Node parent) { } private static class OuterHtmlVisitor implements NodeVisitor { - private Appendable accum; - private Document.OutputSettings out; + private final Appendable accum; + private final Document.OutputSettings out; OuterHtmlVisitor(Appendable accum, Document.OutputSettings out) { this.accum = accum; diff --git a/src/main/java/org/jsoup/nodes/package-info.java b/src/main/java/org/jsoup/nodes/package-info.java index 24b12803ff..7e66d2cab8 100644 --- a/src/main/java/org/jsoup/nodes/package-info.java +++ b/src/main/java/org/jsoup/nodes/package-info.java @@ -1,4 +1,7 @@ /** HTML document structure nodes. */ -package org.jsoup.nodes; \ No newline at end of file +@NonnullByDefault +package org.jsoup.nodes; + +import org.jsoup.internal.NonnullByDefault; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 3c847c3ead..c66ba0aefb 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -12,6 +12,7 @@ import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.io.Reader; import java.io.StringReader; @@ -80,7 +81,7 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { fragmentParsing = false; } - List<Node> parseFragment(String inputFragment, Element context, String baseUri, Parser parser) { + List<Node> parseFragment(String inputFragment, @Nullable Element context, String baseUri, Parser parser) { // context may be null state = HtmlTreeBuilderState.Initial; initialiseParse(new StringReader(inputFragment), baseUri, parser); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 3174dede4f..e781bca81b 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -156,6 +156,25 @@ public class ElementTest { assertEquals("element", p.lastElementSibling().text()); } + @Test public void testFirstElementSiblingOnOrphan() { + Element p = new Element("p"); + assertSame(p, p.firstElementSibling()); + assertSame(p, p.lastElementSibling()); + } + + @Test public void testFirstAndLastSiblings() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three"); + Element div = doc.selectFirst("div"); + Element one = div.child(0); + Element two = div.child(1); + Element three = div.child(2); + + assertSame(one, one.firstElementSibling()); + assertSame(one, two.firstElementSibling()); + assertSame(three, three.lastElementSibling()); + assertSame(three, two.lastElementSibling()); + } + @Test public void testGetParents() { Document doc = Jsoup.parse("<div><p>Hello <span>there</span></div>"); Element span = doc.select("span").first(); @@ -574,6 +593,26 @@ public class ElementTest { assertEquals(ret, p); } + @Test public void testWrapNoop() { + Document doc = Jsoup.parse("<div><p>Hello</p></div>"); + Node p = doc.select("p").first(); + Node wrapped = p.wrap("Some junk"); + assertSame(p, wrapped); + assertEquals("<div><p>Hello</p></div>", TextUtil.stripNewlines(doc.body().html())); + // should be a NOOP + } + + @Test public void testWrapOnOrphan() { + Element orphan = new Element("span").text("Hello!"); + assertFalse(orphan.hasParent()); + Element wrapped = orphan.wrap("<div></div> There!"); + assertSame(orphan, wrapped); + assertTrue(orphan.hasParent()); // should now be in the DIV + assertNotNull(orphan.parent()); + assertEquals("div", orphan.parent().tagName()); + assertEquals("<div>\n <span>Hello!</span>\n</div>", orphan.parent().outerHtml()); + } + @Test public void before() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p1 = doc.select("p").first(); @@ -598,7 +637,24 @@ public class ElementTest { Document doc = Jsoup.parse("<div><p>Hello</p></div>"); Element p = doc.select("p").first(); p.wrap("<div class='head'></div><p>There!</p>"); - assertEquals("<div><div class=\"head\"><p>Hello</p><p>There!</p></div></div>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<div><div class=\"head\"><p>Hello</p></div><p>There!</p></div>", TextUtil.stripNewlines(doc.body().html())); + } + + @Test public void testWrapWithSimpleRemainder() { + Document doc = Jsoup.parse("<p>Hello"); + Element p = doc.selectFirst("p"); + Element body = p.parent(); + assertNotNull(body); + assertEquals("body", body.tagName()); + + p.wrap("<div></div> There"); + Element div = p.parent(); + assertNotNull(div); + assertEquals("div", div.tagName()); + assertSame(div, p.parent()); + assertSame(body, div.parent()); + + assertEquals("<div><p>Hello</p></div> There", TextUtil.stripNewlines(doc.body().html())); } @Test public void testHasText() { @@ -654,6 +710,11 @@ public class ElementTest { assertEquals("<img src=\"foo\">", img.toString()); } + @Test public void orphanDivToString() { + Element orphan = new Element("div").id("foo").text("Hello"); + assertEquals("<div id=\"foo\">\n Hello\n</div>", orphan.toString()); + } + @Test public void testClone() { Document doc = Jsoup.parse("<div><p>One<p><span>Two</div>"); From 415f625565edde917945beedb201285d744365d3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 11:24:54 +1100 Subject: [PATCH 468/774] Updated Document#head and #body to normalize, always return Element Also fixed Cleaner(Whitelist) method --- CHANGES | 3 + src/main/java/org/jsoup/nodes/Document.java | 76 ++++++++++++------- src/main/java/org/jsoup/nodes/Element.java | 6 +- src/main/java/org/jsoup/safety/Cleaner.java | 4 +- src/main/java/org/jsoup/select/Collector.java | 11 ++- src/main/java/org/jsoup/select/Selector.java | 3 +- .../org/jsoup/integration/ConnectTest.java | 3 +- .../java/org/jsoup/nodes/DocumentTest.java | 30 ++++++++ .../org/jsoup/safety/CompatibilityTests.java | 7 ++ 9 files changed, 109 insertions(+), 34 deletions(-) diff --git a/CHANGES b/CHANGES index c54d26eaf6..f0267154a2 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,9 @@ jsoup changelog * Improvement: added Element#id(String) ID attribute setter. + * Improvement: in Document, #body() and #head() accessors will now automatically create those elements, if they were + missing (e.g. if the Document was not parsed from HTML). + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 8a57058835..31315582c6 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -7,6 +7,7 @@ import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; +import org.jsoup.select.Evaluator; import javax.annotation.Nullable; import java.nio.charset.Charset; @@ -77,23 +78,51 @@ else if (!(node instanceof LeafNode)) // scans forward across comments, text, pr return null; // todo - add a set document type? } - + + /** + Find the root HTML element, or create it if it doesn't exist. + @return the root HTML element. + */ + private Element htmlEl() { + for (Element el: childElementsList()) { + if (el.normalName().equals("html")) + return el; + } + return appendElement("html"); + } + /** - Accessor to the document's {@code head} element. - @return {@code head} + Get this document's {@code head} element. + <p> + As a side-effect, if this Document does not already have a HTML structure, it will be created. If you do not want + that, use {@code #selectFirst("head")} instead. + + @return {@code head} element. */ - public @Nullable Element head() { - // todo - we practically enforce this - move to nonnull - return findFirstElementByTagName("head"); + public Element head() { + Element html = htmlEl(); + for (Element el: html.childElementsList()) { + if (el.normalName().equals("head")) + return el; + } + return html.prependElement("head"); } /** - Accessor to the document's {@code body} element. - @return {@code body} + Get this document's {@code body} element. + <p> + As a side-effect, if this Document does not already have a HTML structure, it will be created. If you do not want + that, use {@code #selectFirst("body")} instead. + + @return {@code body} element. */ - public @Nullable Element body() { - // todo - we practically enforce this - move to nonnull - return findFirstElementByTagName("body"); + public Element body() { + Element html = htmlEl(); + for (Element el: html.childElementsList()) { + if (el.normalName().equals("body")) + return el; + } + return html.appendElement("body"); } /** @@ -102,9 +131,10 @@ else if (!(node instanceof LeafNode)) // scans forward across comments, text, pr */ public String title() { // title is a preserve whitespace tag (for document output), but normalised here - Element titleEl = getElementsByTag("title").first(); + Element titleEl = head().selectFirst(titleEval); return titleEl != null ? StringUtil.normaliseWhitespace(titleEl.text()).trim() : ""; } + private static final Evaluator titleEval = new Evaluator.Tag("title"); /** Set the document's {@code title} element. Updates the existing element, or adds {@code title} to {@code head} if @@ -113,12 +143,10 @@ public String title() { */ public void title(String title) { Validate.notNull(title); - Element titleEl = getElementsByTag("title").first(); - if (titleEl == null) { // add to head - head().appendElement("title").text(title); - } else { - titleEl.text(title); - } + Element titleEl = head().selectFirst(titleEval); + if (titleEl == null) // add to head + titleEl = head().appendElement("title"); + titleEl.text(title); } /** @@ -136,17 +164,13 @@ public Element createElement(String tagName) { @return this document after normalisation */ public Document normalise() { - Element htmlEl = findFirstElementByTagName("html"); - if (htmlEl == null) - htmlEl = appendElement("html"); - if (head() == null) - htmlEl.prependElement("head"); - if (body() == null) - htmlEl.appendElement("body"); + Element htmlEl = htmlEl(); // these all create if not found + Element head = head(); + body(); // pull text nodes out of root, html, and head els, and push into body. non-text nodes are already taken care // of. do in inverse order to maintain text order. - normaliseTextNodes(head()); + normaliseTextNodes(head); normaliseTextNodes(htmlEl); normaliseTextNodes(this); diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 052b6352a0..7d891208c1 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -323,7 +323,7 @@ public Elements children() { * TODO - think about pulling this out as a helper as there are other shadow lists (like in Attributes) kept around. * @return a list of child elements */ - private List<Element> childElementsList() { + List<Element> childElementsList() { List<Element> children; if (shadowChildrenRef == null || (children = shadowChildrenRef.get()) == null) { final int size = childNodes.size(); @@ -433,7 +433,7 @@ public Elements select(Evaluator evaluator) { * @param cssQuery cssQuery a {@link Selector} CSS-like query * @return the first matching element, or <b>{@code null}</b> if there is no match. */ - public Element selectFirst(String cssQuery) { + public @Nullable Element selectFirst(String cssQuery) { return Selector.selectFirst(cssQuery, this); } @@ -445,7 +445,7 @@ public Element selectFirst(String cssQuery) { * @return the first matching element (walking down the tree, starting from this element), or {@code null} if none * matchn. */ - public Element selectFirst(Evaluator evaluator) { + public @Nullable Element selectFirst(Evaluator evaluator) { return Collector.findFirst(evaluator, this); } diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 9b6242ff65..6edbf29755 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -33,7 +33,7 @@ </p> */ public class Cleaner { - private Safelist safelist; + private final Safelist safelist; /** Create a new cleaner, that sanitizes documents using the supplied safelist. @@ -51,7 +51,7 @@ public Cleaner(Safelist safelist) { @Deprecated public Cleaner(Whitelist whitelist) { Validate.notNull(whitelist); - new Cleaner((Safelist) whitelist); + this.safelist = whitelist; } /** diff --git a/src/main/java/org/jsoup/select/Collector.java b/src/main/java/org/jsoup/select/Collector.java index de34eddfad..3fdbdad6d0 100644 --- a/src/main/java/org/jsoup/select/Collector.java +++ b/src/main/java/org/jsoup/select/Collector.java @@ -3,6 +3,8 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import javax.annotation.Nullable; + import static org.jsoup.select.NodeFilter.FilterResult.CONTINUE; import static org.jsoup.select.NodeFilter.FilterResult.STOP; @@ -52,7 +54,14 @@ public void tail(Node node, int depth) { } } - public static Element findFirst(Evaluator eval, Element root) { + /** + Finds the first Element that matches the Evaluator that descends from the root, and stops the query once that first + match is found. + @param eval Evaluator to test elements against + @param root root of tree to descend + @return the first match; {@code null} if none + */ + public static @Nullable Element findFirst(Evaluator eval, Element root) { FirstFinder finder = new FirstFinder(root, eval); NodeTraversor.filter(finder, root); return finder.match; diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 61d7f2d0f0..b564a06766 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -3,6 +3,7 @@ import org.jsoup.helper.Validate; import org.jsoup.nodes.Element; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; @@ -155,7 +156,7 @@ static Elements filterOut(Collection<Element> elements, Collection<Element> outs * @param root root element to descend into * @return the matching element, or <b>null</b> if none. */ - public static Element selectFirst(String cssQuery, Element root) { + public static @Nullable Element selectFirst(String cssQuery, Element root) { Validate.notEmpty(cssQuery); return Collector.findFirst(QueryParser.parse(cssQuery), root); } diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 6577c36ba0..91f67a9c64 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -470,9 +470,10 @@ public void testBinaryContentTypeThrowsException() { con.data(FileServlet.ContentTypeParam, "application/rss+xml"); Document doc = con.get(); Element title = doc.selectFirst("title"); + assertNotNull(title); assertEquals("jsoup RSS news", title.text()); assertEquals("channel", title.parent().nodeName()); - assertEquals("jsoup RSS news", doc.title()); + assertEquals("", doc.title()); // the document title is unset, this tag is channel>title, not html>head>title assertEquals(3, doc.select("link").size()); assertEquals("application/rss+xml", con.response().contentType()); assertEquals(Document.OutputSettings.Syntax.xml, doc.outputSettings().syntax()); diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 47177ea4f6..bd0e563dab 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -5,6 +5,8 @@ import org.jsoup.integration.ParseTest; import org.jsoup.nodes.Document.OutputSettings; import org.jsoup.nodes.Document.OutputSettings.Syntax; +import org.jsoup.parser.ParseSettings; +import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -75,6 +77,34 @@ public class DocumentTest { assertEquals("<html><head><script>one</script><noscript>&lt;p&gt;two</noscript> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>three</p><p>four</p></body></html>", TextUtil.stripNewlines(doc.html())); } + @Test public void accessorsWillNormalizeStructure() { + Document doc = new Document(""); + assertEquals("", doc.html()); + + Element body = doc.body(); + assertEquals("body", body.tagName()); + Element head = doc.head(); + assertEquals("head", head.tagName()); + assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", TextUtil.stripNewlines(doc.html())); + } + + @Test public void accessorsAreCaseInsensitive() { + Parser parser = Parser.htmlParser().settings(ParseSettings.preserveCase); + Document doc = parser.parseInput("<!DOCTYPE html><HTML><HEAD><TITLE>SHOUTY</TITLE></HEAD><BODY>HELLO</BODY></HTML>", ""); + + Element body = doc.body(); + assertEquals("BODY", body.tagName()); + assertEquals("body", body.normalName()); + Element head = doc.head(); + assertEquals("HEAD", head.tagName()); + assertEquals("body", body.normalName()); + + Element root = doc.selectFirst("html"); + assertEquals("HTML", root.tagName()); + assertEquals("html", root.normalName()); + assertEquals("SHOUTY", doc.title()); + } + @Test public void testClone() { Document doc = Jsoup.parse("<title>Hello</title> <p>One<p>Two"); Document clone = doc.clone(); diff --git a/src/test/java/org/jsoup/safety/CompatibilityTests.java b/src/test/java/org/jsoup/safety/CompatibilityTests.java index 7586950d1f..5d04883d5c 100644 --- a/src/test/java/org/jsoup/safety/CompatibilityTests.java +++ b/src/test/java/org/jsoup/safety/CompatibilityTests.java @@ -59,6 +59,13 @@ public void handlesFramesets() { assertEquals(0, cleanDoc.body().childNodeSize()); } + @Test public void handlesCleanerFromWhitelist() { + Cleaner cleaner = new Cleaner(Whitelist.basic()); + Document doc = Jsoup.parse("<script>Script</script><p>Text</p>"); + Document clean = cleaner.clean(doc); + assertEquals("<p>Text</p>", clean.body().html()); + } + @Test public void supplyOutputSettings() { // test that one can override the default document output settings From b03e2d2ef38a44bd3e15eb790d6bfcc6aaf5b34f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 11:28:03 +1100 Subject: [PATCH 469/774] Fixed some typos --- CHANGES | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index f0267154a2..a0e88eef96 100644 --- a/CHANGES +++ b/CHANGES @@ -12,7 +12,7 @@ jsoup changelog * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). - * Improvement: reduced thread content in HttpConnection and Document. + * Improvement: reduced thread contention in HttpConnection and Document. <https://github.com/jhy/jsoup/pull/1455> * Improvement: better parsing performance when under high thread concurrency @@ -233,7 +233,7 @@ jsoup changelog Attribute#getValue() method. <https://github.com/jhy/jsoup/issues/1065> - * Bugix: orphan Attribute objects (i.e. created outside of a parse or an Element) would throw an NPE on + * Bugfix: orphan Attribute objects (i.e. created outside of a parse or an Element) would throw an NPE on Attribute#setValue(val) <https://github.com/jhy/jsoup/issues/1107> @@ -381,7 +381,7 @@ jsoup changelog * Updated Jsoup.connect().timeout() to implement a total connect + combined read timeout. Previously it specified connect and buffer read times only, so to implement a combined total timeout, you had to have another thread send - an interupt. + an interrupt. * Improved performance of Node.addChildren (was quadratic) <https://github.com/jhy/jsoup/pull/930> @@ -395,7 +395,7 @@ jsoup changelog * Improved Node traversal, including less object creation, and partial and filtering traversor support. <https://github.com/jhy/jsoup/pull/849> - * Bugfix: if a document was was redecoded after character set detection, the HTML parser was not reset correctly, + * Bugfix: if a document was was re-decoded after character set detection, the HTML parser was not reset correctly, which could lead to an incorrect DOM. <https://github.com/jhy/jsoup/issues/877> @@ -536,7 +536,7 @@ jsoup changelog is not defined by the server, or is defined incorrectly. <https://github.com/jhy/jsoup/issues/743> - * Improved performance of class selectors by reducing memory allocation and garbase collection. + * Improved performance of class selectors by reducing memory allocation and garbage collection. <https://github.com/jhy/jsoup/pull/753> * Improved performance of HTML output by reducing the creation of temporary attribute list iterators. @@ -727,7 +727,7 @@ jsoup changelog * Fixed performance issue when parsing HTML with elements with many children that need re-parenting. <https://github.com/jhy/jsoup/pull/506> - * Fixed an issue where a server returning an unsupport character set response would cause a runtime + * Fixed an issue where a server returning an unsupported character set response would cause a runtime UnsupportedCharsetException, instead of falling back to the default UTF-8 charset. <https://github.com/jhy/jsoup/pull/509> @@ -868,7 +868,7 @@ jsoup changelog * When parsing, allow all tags to self-close. Tags that aren't expected to self-close will get an end tag. <https://github.com/jhy/jsoup/issues/258> - * Fixed an issue when parsing <textarea>/RCData tags containing unescaped closing tags that would drop the traling >. + * Fixed an issue when parsing <textarea>/RCData tags containing unescaped closing tags that would drop the trailing >. * Corrected the javadoc for Element#child() to note that it throws IndexOutOfBounds. <https://github.com/jhy/jsoup/issues/277> @@ -1296,7 +1296,7 @@ jsoup changelog also collection class methods on Elements. * New feature: supports Element#wrap(html) and Elements#wrap(html). * New selector syntax: supports E + F adjacent sibling selector - * New selector systax: supports E ~ F preceding sibling selector + * New selector syntax: supports E ~ F preceding sibling selector * New: supports Element#elementSiblingIndex() * Improved document normalisation. From 9b8e81ab9b7c92d59bfa3977f9ee5cb78ee5b40c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 12:35:54 +1100 Subject: [PATCH 470/774] More nullability assertions --- src/main/java/org/jsoup/Connection.java | 17 +++++------ .../java/org/jsoup/HttpStatusException.java | 4 +-- src/main/java/org/jsoup/Jsoup.java | 3 -- .../jsoup/UnsupportedMimeTypeException.java | 4 +-- src/main/java/org/jsoup/helper/DataUtil.java | 11 ++++---- .../java/org/jsoup/helper/HttpConnection.java | 25 +++++++++-------- .../java/org/jsoup/helper/package-info.java | 4 ++- src/main/java/org/jsoup/nodes/Document.java | 13 ++------- .../java/org/jsoup/nodes/FormElement.java | 2 +- src/main/java/org/jsoup/package-info.java | 3 ++ src/main/java/org/jsoup/select/Collector.java | 5 ++-- .../org/jsoup/select/CombiningEvaluator.java | 3 +- src/main/java/org/jsoup/select/Elements.java | 10 ++++--- src/main/java/org/jsoup/select/Evaluator.java | 28 +++++++++++-------- .../java/org/jsoup/select/NodeTraversor.java | 8 ++++-- .../java/org/jsoup/select/QueryParser.java | 10 +++---- .../org/jsoup/select/StructuralEvaluator.java | 2 +- .../java/org/jsoup/select/package-info.java | 3 ++ .../java/org/jsoup/select/SelectorTest.java | 6 ++++ 19 files changed, 90 insertions(+), 71 deletions(-) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 12d4a615da..d7c2df89a3 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -3,6 +3,7 @@ import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; +import javax.annotation.Nullable; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedInputStream; import java.io.IOException; @@ -217,7 +218,7 @@ public final boolean hasBody() { * @param key the data key * @return null if not set */ - KeyVal data(String key); + @Nullable KeyVal data(String key); /** * Set a POST (or PUT) request body. Useful when a server expects a plain request body, not a set for URL @@ -345,9 +346,9 @@ interface Base<T extends Base> { /** * Get the URL - * @return URL + * @return URL, or {@code null} if it has not yet been set. */ - URL url(); + @Nullable URL url(); /** * Set the URL @@ -358,9 +359,9 @@ interface Base<T extends Base> { /** * Get the request method - * @return method + * @return method, or {@code null} if it has not yet been set. */ - Method method(); + @Nullable Method method(); /** * Set the request method @@ -380,7 +381,7 @@ interface Base<T extends Base> { * @see #hasHeader(String) * @see #cookie(String) */ - String header(String name); + @Nullable String header(String name); /** * Get the values of a header. @@ -494,7 +495,7 @@ interface Request extends Base<Request> { * Get the proxy used for this request. * @return the proxy; <code>null</code> if not enabled. */ - Proxy proxy(); + @Nullable Proxy proxy(); /** * Update the proxy for this request. @@ -786,6 +787,6 @@ interface KeyVal { * Get the current Content Type, or {@code null} if not set. * @return the current Content Type. */ - String contentType(); + @Nullable String contentType(); } } diff --git a/src/main/java/org/jsoup/HttpStatusException.java b/src/main/java/org/jsoup/HttpStatusException.java index 75cd6cdd41..2ad83b7d73 100644 --- a/src/main/java/org/jsoup/HttpStatusException.java +++ b/src/main/java/org/jsoup/HttpStatusException.java @@ -6,8 +6,8 @@ * Signals that a HTTP request resulted in a not OK HTTP response. */ public class HttpStatusException extends IOException { - private int statusCode; - private String url; + private final int statusCode; + private final String url; public HttpStatusException(String message, int statusCode, String url) { super(message); diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 066978a60c..ff74f7a3e2 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -2,7 +2,6 @@ import org.jsoup.helper.DataUtil; import org.jsoup.helper.HttpConnection; -import org.jsoup.internal.ReturnsAreNonnullByDefault; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.jsoup.safety.Cleaner; @@ -10,7 +9,6 @@ import org.jsoup.safety.Whitelist; import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -21,7 +19,6 @@ @author Jonathan Hedley */ -@ParametersAreNonnullByDefault @ReturnsAreNonnullByDefault public class Jsoup { private Jsoup() {} diff --git a/src/main/java/org/jsoup/UnsupportedMimeTypeException.java b/src/main/java/org/jsoup/UnsupportedMimeTypeException.java index 124bc971cd..40fbc87031 100644 --- a/src/main/java/org/jsoup/UnsupportedMimeTypeException.java +++ b/src/main/java/org/jsoup/UnsupportedMimeTypeException.java @@ -6,8 +6,8 @@ * Signals that a HTTP response returned a mime type that is not supported. */ public class UnsupportedMimeTypeException extends IOException { - private String mimeType; - private String url; + private final String mimeType; + private final String url; public UnsupportedMimeTypeException(String message, String mimeType, String url) { super(message); diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 332cccff21..7806bd0efe 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -12,6 +12,7 @@ import org.jsoup.parser.Parser; import org.jsoup.select.Elements; +import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.CharArrayReader; import java.io.File; @@ -109,12 +110,12 @@ static void crossStreams(final InputStream in, final OutputStream out) throws IO } } - static Document parseInputStream(InputStream input, String charsetName, String baseUri, Parser parser) throws IOException { + static Document parseInputStream(@Nullable InputStream input, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { if (input == null) // empty body return new Document(baseUri); input = ConstrainableInputStream.wrap(input, bufferSize, 0); - Document doc = null; + @Nullable Document doc = null; // read the start of the stream and look for a BOM or meta charset input.mark(bufferSize); @@ -226,7 +227,7 @@ static ByteBuffer emptyByteBuffer() { * @param contentType e.g. "text/html; charset=EUC-JP" * @return "EUC-JP", or null if not found. Charset is trimmed and uppercased. */ - static String getCharsetFromContentType(String contentType) { + static @Nullable String getCharsetFromContentType(@Nullable String contentType) { if (contentType == null) return null; Matcher m = charsetPattern.matcher(contentType); if (m.find()) { @@ -237,7 +238,7 @@ static String getCharsetFromContentType(String contentType) { return null; } - private static String validateCharset(String cs) { + private @Nullable static String validateCharset(@Nullable String cs) { if (cs == null || cs.length() == 0) return null; cs = cs.trim().replaceAll("[\"']", ""); try { @@ -262,7 +263,7 @@ static String mimeBoundary() { return StringUtil.releaseBuilder(mime); } - private static BomCharset detectCharsetFromBom(final ByteBuffer byteData) { + private static @Nullable BomCharset detectCharsetFromBom(final ByteBuffer byteData) { final Buffer buffer = byteData; // .mark and rewind used to return Buffer, now ByteBuffer, so cast for backward compat buffer.mark(); byte[] bom = new byte[4]; diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index bf28371e13..6297feb27f 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -10,6 +10,7 @@ import org.jsoup.parser.Parser; import org.jsoup.parser.TokenQueue; +import javax.annotation.Nullable; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedInputStream; @@ -111,8 +112,6 @@ static URL encodeUrl(URL u) { } private static String encodeMimeName(String val) { - if (val == null) - return null; return val.replace("\"", "%22"); } @@ -324,8 +323,8 @@ public Connection postDataCharset(String charset) { @SuppressWarnings({"unchecked"}) private static abstract class Base<T extends Connection.Base> implements Connection.Base<T> { - URL url; - Method method; + @Nullable URL url; + @Nullable Method method; Map<String, List<String>> headers; Map<String, String> cookies; @@ -368,6 +367,7 @@ public String header(String name) { @Override public T addHeader(String name, String value) { Validate.notEmpty(name); + //noinspection ConstantConditions value = value == null ? "" : value; List<String> values = headers(name); @@ -494,7 +494,7 @@ private List<String> getHeadersCaseInsensitive(String name) { return Collections.emptyList(); } - private Map.Entry<String, List<String>> scanHeaders(String name) { + private @Nullable Map.Entry<String, List<String>> scanHeaders(String name) { String lc = lowerCase(name); for (Map.Entry<String, List<String>> entry : headers.entrySet()) { if (lowerCase(entry.getKey()).equals(lc)) @@ -536,18 +536,18 @@ public static class Request extends HttpConnection.Base<Connection.Request> impl System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); // make sure that we can send Sec-Fetch-Site headers etc. } - private Proxy proxy; // nullable + private @Nullable Proxy proxy; private int timeoutMilliseconds; private int maxBodySizeBytes; private boolean followRedirects; - private Collection<Connection.KeyVal> data; - private String body = null; + private final Collection<Connection.KeyVal> data; + private @Nullable String body = null; private boolean ignoreHttpErrors = false; private boolean ignoreContentType = false; private Parser parser; private boolean parserDefined = false; // called parser(...) vs initialized in ctor private String postDataCharset = DataUtil.defaultCharsetName; - private SSLSocketFactory sslSocketFactory; + private @Nullable SSLSocketFactory sslSocketFactory; Request() { timeoutMilliseconds = 30000; // 30 seconds @@ -1001,7 +1001,7 @@ void processResponseHeaders(Map<String, List<String>> resHeaders) { } } - private static String setOutputContentType(final Connection.Request req) { + private @Nullable static String setOutputContentType(final Connection.Request req) { String bound = null; if (req.hasHeader(CONTENT_TYPE)) { // no-op; don't add content type as already set (e.g. for requestBody()) @@ -1024,7 +1024,7 @@ else if (needsMultipart(req)) { return bound; } - private static void writePost(final Connection.Request req, final OutputStream outputStream, final String bound) throws IOException { + private static void writePost(final Connection.Request req, final OutputStream outputStream, @Nullable final String bound) throws IOException { final Collection<Connection.KeyVal> data = req.data(); final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(outputStream, req.postDataCharset())); @@ -1041,7 +1041,8 @@ private static void writePost(final Connection.Request req, final OutputStream o w.write("; filename=\""); w.write(encodeMimeName(keyVal.value())); w.write("\"\r\nContent-Type: "); - w.write(keyVal.contentType() != null ? keyVal.contentType() : DefaultUploadType); + String contentType = keyVal.contentType(); + w.write(contentType != null ? contentType : DefaultUploadType); w.write("\r\n\r\n"); w.flush(); // flush DataUtil.crossStreams(keyVal.inputStream(), outputStream); diff --git a/src/main/java/org/jsoup/helper/package-info.java b/src/main/java/org/jsoup/helper/package-info.java index 592fc022e5..2074b7aafa 100644 --- a/src/main/java/org/jsoup/helper/package-info.java +++ b/src/main/java/org/jsoup/helper/package-info.java @@ -1,5 +1,7 @@ /** Package containing classes supporting the core jsoup code. */ - +@NonnullByDefault package org.jsoup.helper; + +import org.jsoup.internal.NonnullByDefault; diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 31315582c6..a8ddad8643 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -360,20 +360,13 @@ private void ensureMetaCharsetElement() { OutputSettings.Syntax syntax = outputSettings().syntax(); if (syntax == OutputSettings.Syntax.html) { - Element metaCharset = select("meta[charset]").first(); - + Element metaCharset = selectFirst("meta[charset]"); if (metaCharset != null) { metaCharset.attr("charset", charset().displayName()); } else { - Element head = head(); - - if (head != null) { - head.appendElement("meta").attr("charset", charset().displayName()); - } + head().appendElement("meta").attr("charset", charset().displayName()); } - - // Remove obsolete elements - select("meta[name=charset]").remove(); + select("meta[name=charset]").remove(); // Remove obsolete elements } else if (syntax == OutputSettings.Syntax.xml) { Node node = childNodes().get(0); if (node instanceof XmlDeclaration) { diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index e9e8302e9a..261fc1e0a0 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -96,7 +96,7 @@ public List<Connection.KeyVal> formData() { set = true; } if (!set) { - Element option = el.select("option").first(); + Element option = el.selectFirst("option"); if (option != null) data.add(HttpConnection.KeyVal.create(name, option.val())); } diff --git a/src/main/java/org/jsoup/package-info.java b/src/main/java/org/jsoup/package-info.java index 5c5696596a..835aafbae5 100644 --- a/src/main/java/org/jsoup/package-info.java +++ b/src/main/java/org/jsoup/package-info.java @@ -1,4 +1,7 @@ /** Contains the main {@link org.jsoup.Jsoup} class, which provides convenient static access to the jsoup functionality. */ +@NonnullByDefault package org.jsoup; + +import org.jsoup.internal.NonnullByDefault; diff --git a/src/main/java/org/jsoup/select/Collector.java b/src/main/java/org/jsoup/select/Collector.java index 3fdbdad6d0..fb9177aa44 100644 --- a/src/main/java/org/jsoup/select/Collector.java +++ b/src/main/java/org/jsoup/select/Collector.java @@ -15,8 +15,7 @@ */ public class Collector { - private Collector() { - } + private Collector() {} /** Build a list of elements, by visiting root and every descendant of root, and testing it against the evaluator. @@ -69,7 +68,7 @@ public void tail(Node node, int depth) { private static class FirstFinder implements NodeFilter { private final Element root; - private Element match = null; + private @Nullable Element match = null; private final Evaluator eval; FirstFinder(Element root, Evaluator eval) { diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index 7ced9e980a..8974a38bfd 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -3,6 +3,7 @@ import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Element; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -25,7 +26,7 @@ abstract class CombiningEvaluator extends Evaluator { updateNumEvaluators(); } - Evaluator rightMostEvaluator() { + @Nullable Evaluator rightMostEvaluator() { return num > 0 ? evaluators.get(num - 1) : null; } diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 7689021f4d..3bc24c0533 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -9,6 +9,7 @@ import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -183,7 +184,8 @@ public boolean hasClass(String className) { */ public String val() { if (size() > 0) - return first().val(); + //noinspection ConstantConditions + return first().val(); // first() != null as size() > 0 else return ""; } @@ -564,7 +566,7 @@ public Elements prevAll(String query) { return siblings(query, false, true); } - private Elements siblings(String query, boolean next, boolean all) { + private Elements siblings(@Nullable String query, boolean next, boolean all) { Elements els = new Elements(); Evaluator eval = query != null? QueryParser.parse(query) : null; for (Element e : this) { @@ -598,7 +600,7 @@ public Elements parents() { Get the first matched element. @return The first matched element, or <code>null</code> if contents is empty. */ - public Element first() { + public @Nullable Element first() { return isEmpty() ? null : get(0); } @@ -606,7 +608,7 @@ public Element first() { Get the last matched element. @return The last matched element, or <code>null</code> if contents is empty. */ - public Element last() { + public @Nullable Element last() { return isEmpty() ? null : get(size() - 1); } diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index d7d7857558..e086be3740 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -39,7 +39,7 @@ protected Evaluator() { * Evaluator for tag name */ public static final class Tag extends Evaluator { - private String tagName; + private final String tagName; public Tag(String tagName) { this.tagName = tagName; @@ -61,7 +61,7 @@ public String toString() { * Evaluator for tag name that ends with */ public static final class TagEndsWith extends Evaluator { - private String tagName; + private final String tagName; public TagEndsWith(String tagName) { this.tagName = tagName; @@ -82,7 +82,7 @@ public String toString() { * Evaluator for element id */ public static final class Id extends Evaluator { - private String id; + private final String id; public Id(String id) { this.id = id; @@ -104,7 +104,7 @@ public String toString() { * Evaluator for element class */ public static final class Class extends Evaluator { - private String className; + private final String className; public Class(String className) { this.className = className; @@ -126,7 +126,7 @@ public String toString() { * Evaluator for attribute name matching */ public static final class Attribute extends Evaluator { - private String key; + private final String key; public Attribute(String key) { this.key = key; @@ -148,7 +148,7 @@ public String toString() { * Evaluator for attribute name prefix matching */ public static final class AttributeStarting extends Evaluator { - private String keyPrefix; + private final String keyPrefix; public AttributeStarting(String keyPrefix) { Validate.notEmpty(keyPrefix); @@ -504,6 +504,8 @@ public IsNthLastChild(int a, int b) { @Override protected int calculatePosition(Element root, Element element) { + if (element.parent() == null) + return 0; return element.parent().children().size() - element.elementSiblingIndex(); } @@ -524,6 +526,8 @@ public IsNthOfType(int a, int b) { protected int calculatePosition(Element root, Element element) { int pos = 0; + if (element.parent() == null) + return 0; Elements family = element.parent().children(); for (Element el : family) { if (el.tag().equals(element.tag())) pos++; @@ -547,6 +551,8 @@ public IsNthLastOfType(int a, int b) { @Override protected int calculatePosition(Element root, Element element) { int pos = 0; + if (element.parent() == null) + return 0; Elements family = element.parent().children(); for (int i = element.elementSiblingIndex(); i < family.size(); i++) { if (family.get(i).tag().equals(element.tag())) pos++; @@ -656,7 +662,7 @@ public IndexEvaluator(int index) { * Evaluator for matching Element (and its descendants) text */ public static final class ContainsText extends Evaluator { - private String searchText; + private final String searchText; public ContainsText(String searchText) { this.searchText = lowerCase(searchText); @@ -677,7 +683,7 @@ public String toString() { * Evaluator for matching Element (and its descendants) data */ public static final class ContainsData extends Evaluator { - private String searchText; + private final String searchText; public ContainsData(String searchText) { this.searchText = lowerCase(searchText); @@ -698,7 +704,7 @@ public String toString() { * Evaluator for matching Element's own text */ public static final class ContainsOwnText extends Evaluator { - private String searchText; + private final String searchText; public ContainsOwnText(String searchText) { this.searchText = lowerCase(searchText); @@ -719,7 +725,7 @@ public String toString() { * Evaluator for matching Element (and its descendants) text with regex */ public static final class Matches extends Evaluator { - private Pattern pattern; + private final Pattern pattern; public Matches(Pattern pattern) { this.pattern = pattern; @@ -741,7 +747,7 @@ public String toString() { * Evaluator for matching Element's own text with regex */ public static final class MatchesOwn extends Evaluator { - private Pattern pattern; + private final Pattern pattern; public MatchesOwn(Pattern pattern) { this.pattern = pattern; diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index 9f3a567256..99488277d6 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -27,7 +27,9 @@ public static void traverse(NodeVisitor visitor, Node root) { node = node.childNode(0); depth++; } else { - while (node.nextSibling() == null && depth > 0) { + while (true) { + assert node != null; // as depth > 0, will have parent + if (!(node.nextSibling() == null && depth > 0)) break; visitor.tail(node, depth); node = node.parentNode(); depth--; @@ -73,7 +75,9 @@ public static FilterResult filter(NodeFilter filter, Node root) { continue; } // No siblings, move upwards: - while (node.nextSibling() == null && depth > 0) { + while (true) { + assert node != null; // depth > 0, so has parent + if (!(node.nextSibling() == null && depth > 0)) break; // 'tail' current node: if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { result = filter.tail(node, depth); diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 001daf4edd..57636b9be8 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -18,9 +18,9 @@ public class QueryParser { private final static String[] combinators = {",", ">", "+", "~", " "}; private static final String[] AttributeEvals = new String[]{"=", "!=", "^=", "$=", "*=", "~="}; - private TokenQueue tq; - private String query; - private List<Evaluator> evals = new ArrayList<>(); + private final TokenQueue tq; + private final String query; + private final List<Evaluator> evals = new ArrayList<>(); /** * Create a new QueryParser. @@ -95,6 +95,7 @@ private void combinator(char combinator) { // make sure OR (,) has precedence: if (rootEval instanceof CombiningEvaluator.Or && combinator != ',') { currentEval = ((CombiningEvaluator.Or) currentEval).rightMostEvaluator(); + assert currentEval != null; // rightMost signature can return null (if none set), but always will have one by this point replaceRightMost = true; } } @@ -116,12 +117,11 @@ else if (combinator == ',') { // group or. CombiningEvaluator.Or or; if (currentEval instanceof CombiningEvaluator.Or) { or = (CombiningEvaluator.Or) currentEval; - or.add(newEval); } else { or = new CombiningEvaluator.Or(); or.add(currentEval); - or.add(newEval); } + or.add(newEval); currentEval = or; } else diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index 4ac28f2842..979e40c0b5 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -58,7 +58,7 @@ public boolean matches(Element root, Element element) { return false; Element parent = element.parent(); - while (true) { + while (parent != null) { if (evaluator.matches(root, parent)) return true; if (parent == root) diff --git a/src/main/java/org/jsoup/select/package-info.java b/src/main/java/org/jsoup/select/package-info.java index e6871fc9e6..5bbdb85543 100644 --- a/src/main/java/org/jsoup/select/package-info.java +++ b/src/main/java/org/jsoup/select/package-info.java @@ -2,4 +2,7 @@ Packages to support the CSS-style element selector. {@link org.jsoup.select.Selector Selector defines the query syntax.} */ +@NonnullByDefault package org.jsoup.select; + +import org.jsoup.internal.NonnullByDefault; diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 347f80cc06..2602c4b546 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -843,6 +843,12 @@ public void containsData(Locale locale) { assertEquals("Two", doc.select("p:matchText + br + *").text()); } + @Test public void nthLastChildWithNoParent() { + Element el = new Element("p").text("Orphan"); + Elements els = el.select("p:nth-last-child(1)"); + assertEquals(0, els.size()); + } + @Test public void splitOnBr() { String html = "<div><p>One<br>Two<br>Three</p></div>"; Document doc = Jsoup.parse(html); From 1350463cd819c924497f1cc260ab0096ea270e34 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 12:51:47 +1100 Subject: [PATCH 471/774] Make sure org.jsoup.internal gets exported in manifest So that it can include javax annotations. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 7de140e3e1..105b546f9b 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,7 @@ <configuration> <instructions> <Bundle-DocURL>https://jsoup.org/</Bundle-DocURL> + <Export-Package>org.jsoup.*</Export-Package> </instructions> </configuration> </plugin> From 532b79a61ae8191e2927c59300af68d18a54aa4e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 14:11:32 +1100 Subject: [PATCH 472/774] Clone outputsettings from dirty to clean document --- CHANGES | 4 ++++ src/main/java/org/jsoup/safety/Cleaner.java | 7 ++++--- src/test/java/org/jsoup/safety/CleanerTest.java | 11 +++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index a0e88eef96..1ffff3093a 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,10 @@ jsoup changelog * Improvement: in Document, #body() and #head() accessors will now automatically create those elements, if they were missing (e.g. if the Document was not parsed from HTML). + * Improvement: when cleaning a document, the output settings of the original document are cloned into the cleaned + document. + <https://github.com/jhy/jsoup/issues/1417> + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 6edbf29755..bbe44ce458 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -56,7 +56,8 @@ public Cleaner(Whitelist whitelist) { /** Creates a new, clean document, from the original dirty document, containing only elements allowed by the safelist. - The original document is not modified. Only elements from the dirt document's <code>body</code> are used. + The original document is not modified. Only elements from the dirty document's <code>body</code> are used. The + OutputSettings of the original document are cloned into the clean document. @param dirtyDocument Untrusted base document to clean. @return cleaned document. */ @@ -64,8 +65,8 @@ public Document clean(Document dirtyDocument) { Validate.notNull(dirtyDocument); Document clean = Document.createShell(dirtyDocument.baseUri()); - if (dirtyDocument.body() != null) // frameset documents won't have a body. the clean doc will have empty body. - copySafeNodes(dirtyDocument.body(), clean.body()); + copySafeNodes(dirtyDocument.body(), clean.body()); + clean.outputSettings(dirtyDocument.outputSettings().clone()); return clean; } diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index bce555d914..33380549c6 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -328,4 +328,15 @@ public void bailsIfRemovingProtocolThatsNotSet() { assertEquals(orig, TextUtil.stripNewlines(clean)); // only difference is pretty print wrap & indent assertTrue(isValid); } + + @Test public void copiesOutputSettings() { + Document orig = Jsoup.parse("<p>test<br></p>"); + orig.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + orig.outputSettings().escapeMode(Entities.EscapeMode.xhtml); + Safelist whitelist = Safelist.none().addTags("p", "br"); + + Document result = new Cleaner(whitelist).clean(orig); + assertEquals(Document.OutputSettings.Syntax.xml, result.outputSettings().syntax()); + assertEquals("<p>test<br /></p>", result.body().html()); + } } From 20797a47313b115eb220f4df74d39da9de94f5e0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 14:35:39 +1100 Subject: [PATCH 473/774] Disable pretty printing of XML input by default --- CHANGES | 2 ++ src/main/java/org/jsoup/parser/XmlTreeBuilder.java | 4 +++- .../java/org/jsoup/parser/XmlTreeBuilderTest.java | 14 ++++++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 1ffff3093a..257f5d77fd 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,8 @@ jsoup changelog document. <https://github.com/jhy/jsoup/issues/1417> + * Improvement: when parsing XML, disable pretty-printing by default. + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 1be8bf1e42..b85cc3f585 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -31,7 +31,9 @@ ParseSettings defaultSettings() { protected void initialiseParse(Reader input, String baseUri, Parser parser) { super.initialiseParse(input, baseUri, parser); stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) - doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + doc.outputSettings() + .syntax(Document.OutputSettings.Syntax.xml) + .prettyPrint(false); // as XML, we don't understand what whitespace is significant or not } Document parse(Reader input, String baseUri) { diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 8aa7547a27..b51755db29 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -103,8 +103,7 @@ public void testDoesNotForceSelfClosingKnownTags() { @Test public void handlesXmlDeclarationAsDeclaration() { String html = "<?xml encoding='UTF-8' ?><body>One</body><!-- comment -->"; Document doc = Jsoup.parse(html, "", Parser.xmlParser()); - assertEquals("<?xml encoding=\"UTF-8\"?> <body> One </body><!-- comment -->", - StringUtil.normaliseWhitespace(doc.outerHtml())); + assertEquals("<?xml encoding=\"UTF-8\"?><body>One</body><!-- comment -->",doc.outerHtml()); assertEquals("#declaration", doc.childNode(0).nodeName()); assertEquals("#comment", doc.childNode(2).nodeName()); } @@ -188,6 +187,13 @@ public void appendPreservesCaseByDefault() { assertEquals("<One>One<Two ID=\"2\">Two</Two></One>", TextUtil.stripNewlines(doc.html())); } + @Test + public void disablesPrettyPrintingByDefault() { + String xml = "\n\n<div><one>One</one><one>\n Two</one>\n</div>\n "; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + assertEquals(xml, doc.html()); + } + @Test public void canNormalizeCase() { String xml = "<TEST ID=1>Check</TEST>"; @@ -198,7 +204,7 @@ public void canNormalizeCase() { @Test public void normalizesDiscordantTags() { Parser parser = Parser.xmlParser().settings(ParseSettings.htmlDefault); Document document = Jsoup.parse("<div>test</DIV><p></p>", "", parser); - assertEquals("<div>\n test\n</div>\n<p></p>", document.html()); + assertEquals("<div>test</div><p></p>", document.html()); // was failing -> toString() = "<div>\n test\n <p></p>\n</div>" } @@ -211,7 +217,7 @@ public void canNormalizeCase() { assertEquals(0, div.children().size()); assertEquals(1, div.childNodeSize()); // no elements, one text node - assertEquals("<div id=\"1\"><![CDATA[\n<html>\n <foo><&amp;]]>\n</div>", div.outerHtml()); + assertEquals("<div id=\"1\"><![CDATA[\n<html>\n <foo><&amp;]]></div>", div.outerHtml()); CDataNode cdata = (CDataNode) div.textNodes().get(0); assertEquals("\n<html>\n <foo><&amp;", cdata.text()); From 3c37bffed94c19c5f500217eb568bcdf394be64e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 21 Dec 2020 14:37:53 +1100 Subject: [PATCH 474/774] Link to issue --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 257f5d77fd..a9874a24db 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ jsoup changelog <https://github.com/jhy/jsoup/issues/1417> * Improvement: when parsing XML, disable pretty-printing by default. + <https://github.com/jhy/jsoup/issues/1168> * Build Improvement: moved to GitHub Workflows for build verification. From afd73606a90909444e1c443b555dae7b71e6a5a0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 22 Dec 2020 15:21:49 +1100 Subject: [PATCH 475/774] Performance improvement for clone The setBaseUri call was redundant now that the base URI is lazy loaded by an element, by walking up the parent stack to find it. But because of that walk, the performance of setBaseUri during clone was ~ O(n^2) or worse. Also, updated StringUtil to limit padding to 30 wide, otherwise the outerHtml string format made in hasSameValue was blowing the heap for very deep nodes. --- CHANGES | 3 +++ .../java/org/jsoup/internal/StringUtil.java | 4 +++- src/main/java/org/jsoup/nodes/Element.java | 1 - src/main/java/org/jsoup/nodes/Node.java | 3 ++- .../org/jsoup/internal/StringUtilTest.java | 2 +- .../java/org/jsoup/nodes/DocumentTest.java | 18 +++++++++++------- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index a9874a24db..6a93ad4871 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,9 @@ jsoup changelog * Improvement: when parsing XML, disable pretty-printing by default. <https://github.com/jhy/jsoup/issues/1168> + * Improvement: much better performance in Node#clone() for large and deeply nested documents. Complexity was O(n^2) or + worse, now O(n). + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index b313503155..a3ad0d316e 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -17,6 +17,7 @@ public final class StringUtil { static final String[] padding = {"", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}; + private static final int maxPaddingWidth = 30; // so very deeply nested nodes don't get insane padding amounts /** * Join a collection of strings by a separator @@ -61,7 +62,7 @@ public static String join(String[] strings, String sep) { } /** - * Returns space padding + * Returns space padding (up to a max of 30). * @param width amount of padding desired * @return string of spaces * width */ @@ -71,6 +72,7 @@ public static String padding(int width) { if (width < padding.length) return padding[width]; + width = Math.min(width, maxPaddingWidth); char[] out = new char[width]; for (int i = 0; i < width; i++) out[i] = ' '; diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 7d891208c1..1686e722c4 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1587,7 +1587,6 @@ protected Element doClone(@Nullable Node parent) { clone.attributes = attributes != null ? attributes.clone() : null; clone.childNodes = new NodeList(clone, childNodes.size()); clone.childNodes.addAll(childNodes); // the children then get iterated and cloned in Node.clone - clone.setBaseUri(baseUri()); return clone; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index b1e058888d..aa184a9a1b 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -662,9 +662,10 @@ protected void indent(Appendable accum, int depth, Document.OutputSettings out) /** * Check if this node is the same instance of another (object identity test). + * <p>For an node value equality check, see {@link #hasSameValue(Object)}</p> * @param o other object to compare to * @return true if the content of this node is the same as the other - * @see Node#hasSameValue(Object) to compare nodes by their value + * @see Node#hasSameValue(Object) */ @Override public boolean equals(Object o) { diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index c674b97e6c..f53cb8448d 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -24,7 +24,7 @@ public void join() { assertEquals(" ", StringUtil.padding(1)); assertEquals(" ", StringUtil.padding(2)); assertEquals(" ", StringUtil.padding(15)); - assertEquals(" ", StringUtil.padding(45)); + assertEquals(" ", StringUtil.padding(45)); // we tap out at 30 } @Test public void paddingInACan() { diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index bd0e563dab..2bc345b2c2 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -178,17 +178,21 @@ public class DocumentTest { assertEquals(htmlContent, document.html(new StringWriter()).toString()); } - // Ignored since this test can take awhile to run. - @Disabled @Test public void testOverflowClone() { - StringBuilder builder = new StringBuilder(); + StringBuilder sb = new StringBuilder(); + sb.append("<head><base href='https://jsoup.org/'>"); for (int i = 0; i < 100000; i++) { - builder.insert(0, "<i>"); - builder.append("</i>"); + sb.append("<div>"); } + sb.append("<p>Hello <a href='/example.html'>there</a>"); - Document doc = Jsoup.parse(builder.toString()); - doc.clone(); + Document doc = Jsoup.parse(sb.toString()); + + String expectedLink = "https://jsoup.org/example.html"; + assertEquals(expectedLink, doc.selectFirst("a").attr("abs:href")); + Document clone = doc.clone(); + doc.hasSameValue(clone); + assertEquals(expectedLink, clone.selectFirst("a").attr("abs:href")); } @Test public void DocumentsWithSameContentAreEqual() { From 724b2c5bf576cbd548738756bfe5f7a7b90c6239 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 22 Dec 2020 19:25:40 +1100 Subject: [PATCH 476/774] When parsing fragments, use the context node to insert children That ensures data for script/style will be treated as DataNode, not TextNode Fixes #1419 --- CHANGES | 9 +++ src/main/java/org/jsoup/nodes/Element.java | 28 +++++---- src/main/java/org/jsoup/nodes/Node.java | 6 +- .../org/jsoup/parser/HtmlTreeBuilder.java | 21 ++++++- src/main/java/org/jsoup/parser/Parser.java | 8 +++ src/main/java/org/jsoup/parser/Token.java | 10 +++- .../java/org/jsoup/parser/TreeBuilder.java | 9 ++- .../java/org/jsoup/nodes/ElementTest.java | 60 +++++++++++++++++++ .../parser/HtmlTreeBuilderStateTest.java | 3 +- 9 files changed, 131 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index 6a93ad4871..225c32dd6a 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,15 @@ jsoup changelog * Bugfix: when wrapping an element with HTML that included multiple sibling elements, those siblings were incorrectly added as children of the wrapper instead of siblings. + * Bugfix: when setting the content of a script or style tag via the Element#html(String) method, the content is + treated as a DataNode, not a TextNode. This means that characters like '<' will no longer be incorrectly escaped. + As a related ergonomic improvement, the same behavior applies for Element#text(String) (i.e. the content will be + treated as a DataNode, despite calling the text() method. + <https://github.com/jhy/jsoup/issues/1419> + + * Bugfix: when wrapping HTML around an existing element with Element#wrap(String), will no take the content as + provided and ignore normal HTML tree-building rules. This allows for e.g. a div tag to be placed inside of p tags. + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 1686e722c4..376a281c11 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -2,9 +2,7 @@ import org.jsoup.helper.ChangeNotifyingArrayList; import org.jsoup.helper.Validate; -import org.jsoup.internal.FieldsAreNonnullByDefault; import org.jsoup.internal.NonnullByDefault; -import org.jsoup.internal.ReturnsAreNonnullByDefault; import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Tag; @@ -18,7 +16,6 @@ import org.jsoup.select.Selector; import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -1150,12 +1147,13 @@ public Elements getAllElements() { /** Gets the <b>normalized, combined text</b> of this element and all its children. Whitespace is normalized and trimmed. - <p> - For example, given HTML {@code <p>Hello <b>there</b> now! </p>}, {@code p.text()} returns {@code "Hello there + <p>For example, given HTML {@code <p>Hello <b>there</b> now! </p>}, {@code p.text()} returns {@code "Hello there now!"} - <p> - If you do not want normalized text, use {@link #wholeText()}. If you want just the text of this node (and not + <p>If you do not want normalized text, use {@link #wholeText()}. If you want just the text of this node (and not children), use {@link #ownText()} + <p>Note that this method returns the textual content that would be presented to a reader. The contents of data + nodes (such as {@code <script>} tags are not considered text. Use {@link #data()} or {@link #html()} to retrieve + that content. @return unencoded, normalized text, or empty string if none. @see #wholeText() @@ -1274,16 +1272,22 @@ static boolean preserveWhitespace(@Nullable Node node) { } /** - * Set the text of this element. Any existing contents (text or elements) will be cleared + * Set the text of this element. Any existing contents (text or elements) will be cleared. + * <p>As a special case, for {@code <script>} and {@code <style> tags, the input text will be treated as data, + * not visible text.</p> * @param text unencoded text * @return this element */ public Element text(String text) { Validate.notNull(text); - empty(); - TextNode textNode = new TextNode(text); - appendChild(textNode); + // special case for script/style in HTML: should be data node + Document owner = ownerDocument(); + // an alternate impl would be to run through the parser + if (owner != null && owner.parser().isContentForTagData(normalName())) + appendChild(new DataNode(text)); + else + appendChild(new TextNode(text)); return this; } @@ -1308,7 +1312,7 @@ public boolean hasText() { } /** - * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. Note that data is NOT the + * Get the combined data of this element. Data is e.g. the inside of a {@code <script>} tag. Note that data is NOT the * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@code data()} * for the contents of scripts, comments, CSS styles, etc. * diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index aa184a9a1b..3030329635 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -357,10 +357,10 @@ private void addSiblingHtml(int index, String html) { public Node wrap(String html) { Validate.notEmpty(html); - // Parse context - this if element or parent if element else null + // Parse context - parent (because wrapping), this, or null Element context = - this instanceof Element ? (Element) this : - parentNode != null && parentNode instanceof Element ? (Element) parentNode : + parentNode != null && parentNode instanceof Element ? (Element) parentNode : + this instanceof Element ? (Element) this : null; List<Node> wrapChildren = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); Node wrapNode = wrapChildren.get(0); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index c66ba0aefb..ddb9bd01a2 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -108,7 +108,7 @@ else if (contextTag.equals("plaintext")) else tokeniser.transition(TokeniserState.Data); // default - root = new Element(Tag.valueOf("html", settings), baseUri); + root = new Element(Tag.valueOf(contextTag, settings), baseUri); doc.appendChild(root); stack.add(root); resetInsertionMode(); @@ -126,8 +126,14 @@ else if (contextTag.equals("plaintext")) } runParser(); - if (context != null) + if (context != null) { + // depending on context and the input html, content may have been added outside of the root el + // e.g. context=p, input=div, the div will have been pushed out. + List<Node> nodes = root.siblingNodes(); + if (!nodes.isEmpty()) + root.insertChildren(-1, nodes); return root.childNodes(); + } else return doc.childNodes(); } @@ -271,7 +277,7 @@ void insert(Token.Character characterToken) { if (characterToken.isCData()) node = new CDataNode(data); - else if (tagName.equals("script") || tagName.equals("style")) + else if (isContentForTagData(tagName)) node = new DataNode(data); else node = new TextNode(data); @@ -743,4 +749,13 @@ public String toString() { ", currentElement=" + currentElement() + '}'; } + + private static final String[] dataTags = {"script", "style"}; + protected boolean isContentForTagData(String normalName) { + for (String tag : dataTags) { + if (tag.equals(normalName)) + return true; + } + return false; + } } diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index ae64918dc9..2c75538474 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -93,6 +93,14 @@ public ParseSettings settings() { return settings; } + /** + (An internal method, visible for Element. For HTML parse, signals that script and style text should be treated as + Data Nodes). + */ + public boolean isContentForTagData(String normalName) { + return getTreeBuilder().isContentForTagData(normalName); + } + // static parse functions below /** * Parse HTML into a Document. diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 56f72b5885..4cdde952ca 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -149,6 +149,10 @@ final String normalName() { // lower case, used in tree building for working out return normalName; } + final String toStringName() { + return tagName != null ? tagName : "[unset]"; + } + final Tag name(String name) { tagName = name; normalName = lowerCase(name); @@ -240,9 +244,9 @@ StartTag nameAttr(String name, Attributes attributes) { @Override public String toString() { if (hasAttributes() && attributes.size() > 0) - return "<" + name() + " " + attributes.toString() + ">"; + return "<" + toStringName() + " " + attributes.toString() + ">"; else - return "<" + name() + ">"; + return "<" + toStringName() + ">"; } } @@ -254,7 +258,7 @@ final static class EndTag extends Tag{ @Override public String toString() { - return "</" + (tagName != null ? tagName : "(unset)") + ">"; + return "</" + toStringName() + ">"; } } diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 5c1ecfbfba..baa45037ec 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -108,7 +108,6 @@ protected Element currentElement() { return size > 0 ? stack.get(size-1) : null; } - /** * If the parser is tracking errors, and an error at the current position. * @param msg error message @@ -118,4 +117,12 @@ protected void error(String msg) { if (errors.canAddError()) errors.add(new ParseError(reader.pos(), msg)); } + + /** + (An internal method, visible for Element. For HTML parse, signals that script and style text should be treated as + Data Nodes). + */ + protected boolean isContentForTagData(String normalName) { + return false; + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index e781bca81b..62e2114317 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.*; import org.junit.jupiter.api.Test; @@ -613,6 +614,15 @@ public class ElementTest { assertEquals("<div>\n <span>Hello!</span>\n</div>", orphan.parent().outerHtml()); } + @Test public void testWrapArtificialStructure() { + // div normally couldn't get into a p, but explicitly want to wrap + Document doc = Jsoup.parse("<p>Hello <i>there</i> now."); + Element i = doc.selectFirst("i"); + i.wrap("<div id=id1></div> quite"); + assertEquals("div", i.parent().tagName()); + assertEquals("<p>Hello <div id=\"id1\"><i>there</i></div> quite now.</p>", TextUtil.stripNewlines(doc.body().html())); + } + @Test public void before() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p1 = doc.select("p").first(); @@ -1704,4 +1714,54 @@ public void testChildSizeWithMixedContent() { assertTrue(doc.selectFirst("p").isBlock()); assertFalse(doc.selectFirst("span").isBlock()); } + + @Test public void testScriptTextHtmlSetAsData() { + String src = "var foo = 5 < 2;\nvar bar = 1 && 2;"; + String html = "<script>" + src + "</script>"; + Document doc = Jsoup.parse(html); + Element el = doc.selectFirst("script"); + assertNotNull(el); + + validateScriptContents(src, el); + + src = "var foo = 4 < 2;\nvar bar > 1 && 2;"; + el.html(src); + validateScriptContents(src, el); + + // special case for .text (in HTML; in XML will just be regular text) + el.text(src); + validateScriptContents(src, el); + + // XML, no special treatment, get escaped correctly + Document xml = Parser.xmlParser().parseInput(html, ""); + Element xEl = xml.selectFirst("script"); + assertNotNull(xEl); + src = "var foo = 5 < 2;\nvar bar = 1 && 2;"; + String escaped = "var foo = 5 &lt; 2;\nvar bar = 1 &amp;&amp; 2;"; + validateXmlScriptContents(xEl); + xEl.text(src); + validateXmlScriptContents(xEl); + xEl.html(src); + validateXmlScriptContents(xEl); + + assertEquals("<script>var foo = 4 < 2;\nvar bar > 1 && 2;</script>", el.outerHtml()); + assertEquals("<script>" + escaped + "</script>", xEl.outerHtml()); // escaped in xml as no special treatment + + } + + private static void validateScriptContents(String src, Element el) { + assertEquals("", el.text()); // it's not text + assertEquals("", el.ownText()); + assertEquals("", el.wholeText()); + assertEquals(src, el.html()); + assertEquals(src, el.data()); + } + + private static void validateXmlScriptContents(Element el) { + assertEquals("var foo = 5 < 2; var bar = 1 && 2;", el.text()); + assertEquals("var foo = 5 < 2; var bar = 1 && 2;", el.ownText()); + assertEquals("var foo = 5 < 2;\nvar bar = 1 && 2;", el.wholeText()); + assertEquals("var foo = 5 &lt; 2;\nvar bar = 1 &amp;&amp; 2;", el.html()); + assertEquals("", el.data()); + } } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index ea234d8439..3aea3bdfd1 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -18,7 +18,8 @@ static List<Object[]> findConstantArrays(Class aClass) { Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { - if (Modifier.isStatic(field.getModifiers()) && field.getType().isArray()) { + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && field.getType().isArray()) { try { array.add((Object[]) field.get(null)); } catch (IllegalAccessException e) { From 690d601950bf44fc84dcc711b2ef265f9542df62 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 23 Dec 2020 10:33:14 +1100 Subject: [PATCH 477/774] Test for body() not null on frameset Was implemented with commit 415f625565edde917945beedb201285d744365d3 Closes #1453 --- .../java/org/jsoup/nodes/DocumentTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 2bc345b2c2..aebaa9a1b5 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -489,4 +489,34 @@ public void testShiftJisRoundtrip() throws Exception { assertNotNull(documentType); assertEquals("html", documentType.name()); } + + @Test public void framesetSupportsBodyMethod() { + String html = "<html><head><title>Frame Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><frameset id=id><frame src=foo.html></frameset>"; + Document doc = Jsoup.parse(html); + Element head = doc.head(); + assertNotNull(head); + assertEquals("Frame Test", doc.title()); + + // Frameset docs per html5 spec have no body. + assertNull(doc.selectFirst("body")); + + // but we want the .body() tag to not null; so we auto-normalize it + // doing it in body() vs parse keeps the html close to original for round-trip option + Element body = doc.body(); + assertNotNull(body); + assertEquals("", body.html()); + + assertNotNull(doc.selectFirst("body")); + + String expected = "<html>\n" + + " <head>\n" + + " <title>Frame Test</title>\n" + + " <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + + " <frameset id=\"id\">\n" + + " <frame src=\"foo.html\">\n" + + " </frameset>\n" + + " <body></body>\n" + + "</html>"; + assertEquals(expected, doc.html()); + } } From 3f75b90de261b6a19121fd07cd477f45fc500bde Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 23 Dec 2020 10:47:08 +1100 Subject: [PATCH 478/774] Test case for shallowClone.toString Closes #1410 --- src/test/java/org/jsoup/nodes/ElementTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 62e2114317..8b3eb76b58 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1764,4 +1764,19 @@ private static void validateXmlScriptContents(Element el) { assertEquals("var foo = 5 &lt; 2;\nvar bar = 1 &amp;&amp; 2;", el.html()); assertEquals("", el.data()); } + + @Test public void testShallowCloneToString() { + // https://github.com/jhy/jsoup/issues/1410 + Document doc = Jsoup.parse("<p><i>Hello</i></p>"); + Element p = doc.selectFirst("p"); + Element i = doc.selectFirst("i"); + String pH = p.shallowClone().toString(); + String iH = i.shallowClone().toString(); + + assertEquals("<p></p>", pH); // shallow, so no I + assertEquals("<i></i>", iH); + + assertEquals(p.outerHtml(), p.toString()); + assertEquals(i.outerHtml(), i.toString()); + } } From 6a047bfe495d4c8ae827c9522fbf3fa525d8fdd7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 23 Dec 2020 12:43:08 +1100 Subject: [PATCH 479/774] Testcase for Safelist extension Closes #937 --- .../integration/SafelistExtensionTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/test/java/org/jsoup/integration/SafelistExtensionTest.java diff --git a/src/test/java/org/jsoup/integration/SafelistExtensionTest.java b/src/test/java/org/jsoup/integration/SafelistExtensionTest.java new file mode 100644 index 0000000000..82f9316aaa --- /dev/null +++ b/src/test/java/org/jsoup/integration/SafelistExtensionTest.java @@ -0,0 +1,49 @@ +package org.jsoup.integration; + +import org.jsoup.Jsoup; +import org.jsoup.TextUtil; +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Element; +import org.jsoup.safety.Safelist; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + Check that we can extend Safelist methods + */ +public class SafelistExtensionTest { + @Test public void canCustomizeSafeTests() { + OpenSafelist openSafelist = new OpenSafelist(Safelist.relaxed()); + Safelist safelist = Safelist.relaxed(); + + String html = "<p><opentag openattr>Hello</opentag></p>"; + + String openClean = Jsoup.clean(html, openSafelist); + String clean = Jsoup.clean(html, safelist); + + assertEquals("<p><opentag openattr=\"\">Hello</opentag></p>", TextUtil.stripNewlines(openClean)); + assertEquals("<p>Hello</p>", clean); + } + + // passes tags and attributes starting with "open" + private static class OpenSafelist extends Safelist { + public OpenSafelist(Safelist safelist) { + super(safelist); + } + + @Override + protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) { + if (attr.getKey().startsWith("open")) + return true; + return super.isSafeAttribute(tagName, el, attr); + } + + @Override + protected boolean isSafeTag(String tag) { + if (tag.startsWith("open")) + return true; + return super.isSafeTag(tag); + } + } +} From 982c993a1b886a807d7facc47dcb29dbed2961f3 Mon Sep 17 00:00:00 2001 From: Arnaud Pflieger <pflieger.arnaud@gmail.com> Date: Mon, 15 Jun 2020 17:26:09 +0200 Subject: [PATCH 480/774] Fix Elements.forms() the forms() method used to return FormElement-s that are directly in the Elements collection. This behavior was broken since a657ae0240b875a703a1e4909d56ad59997d362d Fixes #1384 --- src/main/java/org/jsoup/select/Elements.java | 26 +++++++++---------- .../java/org/jsoup/select/ElementsTest.java | 9 ++----- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 3bc24c0533..88dc51f17f 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -638,7 +638,11 @@ public Elements filter(NodeFilter nodeFilter) { * no forms. */ public List<FormElement> forms() { - return nodesOfType(FormElement.class); + ArrayList<FormElement> forms = new ArrayList<>(); + for (Element el: this) + if (el instanceof FormElement) + forms.add((FormElement) el); + return forms; } /** @@ -646,7 +650,7 @@ public List<FormElement> forms() { * @return Comment nodes, or an empty list if none. */ public List<Comment> comments() { - return nodesOfType(Comment.class); + return childNodesOfType(Comment.class); } /** @@ -654,7 +658,7 @@ public List<Comment> comments() { * @return TextNode nodes, or an empty list if none. */ public List<TextNode> textNodes() { - return nodesOfType(TextNode.class); + return childNodesOfType(TextNode.class); } /** @@ -663,20 +667,16 @@ public List<TextNode> textNodes() { * @return Comment nodes, or an empty list if none. */ public List<DataNode> dataNodes() { - return nodesOfType(DataNode.class); + return childNodesOfType(DataNode.class); } - private <T extends Node> List<T> nodesOfType(Class<T> tClass) { + private <T extends Node> List<T> childNodesOfType(Class<T> tClass) { ArrayList<T> nodes = new ArrayList<>(); for (Element el: this) { - if (el.getClass().isInstance(tClass)) { // Handles FormElements - nodes.add(tClass.cast(el)); - } else if (Node.class.isAssignableFrom(tClass)) { // check if child nodes match - for (int i = 0; i < el.childNodeSize(); i++) { - Node node = el.childNode(i); - if (tClass.isInstance(node)) - nodes.add(tClass.cast(node)); - } + for (int i = 0; i < el.childNodeSize(); i++) { + Node node = el.childNode(i); + if (tClass.isInstance(node)) + nodes.add(tClass.cast(node)); } } return nodes; diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index c41a01b9aa..20399e2e22 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -282,8 +282,8 @@ public void tail(Node node, int depth) { @Test public void forms() { Document doc = Jsoup.parse("<form id=1><input name=q></form><div /><form id=2><input name=f></form>"); - Elements els = doc.select("*"); - assertEquals(9, els.size()); + Elements els = doc.select("form, div"); + assertEquals(3, els.size()); List<FormElement> forms = els.forms(); assertEquals(2, forms.size()); @@ -337,11 +337,6 @@ public void tail(Node node, int depth) { assertEquals(0, doc.select("form").textNodes().size()); } - @Test public void formElementsDescendButNotAccumulate() { - Document doc = Jsoup.parse("<div><div><form id=1>"); - assertEquals(1, doc.select("div").forms().size()); - } - @Test public void classWithHyphen() { Document doc = Jsoup.parse("<p class='tab-nav'>Check</p>"); Elements els = doc.getElementsByClass("tab-nav"); From 1b170925e005cb7d42c874827d72d677cfee74b2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 28 Dec 2020 12:48:54 +1100 Subject: [PATCH 481/774] Reimplemented the form upload test --- CHANGES | 3 ++ .../org/jsoup/integration/ConnectTest.java | 35 +++++++++++++++++++ .../org/jsoup/integration/UrlConnectTest.java | 27 -------------- src/test/resources/htmltests/upload-form.html | 16 +++++++++ 4 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 src/test/resources/htmltests/upload-form.html diff --git a/CHANGES b/CHANGES index 225c32dd6a..d9dbf8f366 100644 --- a/CHANGES +++ b/CHANGES @@ -64,6 +64,9 @@ jsoup changelog * Bugfix: when wrapping HTML around an existing element with Element#wrap(String), will no take the content as provided and ignore normal HTML tree-building rules. This allows for e.g. a div tag to be placed inside of p tags. + * Bugfix: the Elements#forms() method should return the selected immediate elements that are Forms, not children. + <https://github.com/jhy/jsoup/pull/1403> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 91f67a9c64..01bf601842 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -6,6 +6,7 @@ import org.jsoup.integration.servlets.*; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.FormElement; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -15,6 +16,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.List; import java.util.Map; import static org.jsoup.helper.HttpConnection.CONTENT_TYPE; @@ -506,4 +508,37 @@ public void handlesUnknownEscapesAcrossBuffer() throws IOException { assertEquals(text, docFromLocalServer.body().text()); assertEquals(text, docFromFileRead.body().text()); } + + /** + * Test fetching a form, and submitting it with a file attached. + */ + @Test + public void postHtmlFile() throws IOException { + Document index = Jsoup.connect(FileServlet.urlTo("/htmltests/upload-form.html")).get(); + List<FormElement> forms = index.select("[name=tidy]").forms(); + assertEquals(1, forms.size()); + FormElement form = forms.get(0); + Connection post = form.submit(); + + File uploadFile = ParseTest.getFile("/htmltests/google-ipod.html.gz"); + FileInputStream stream = new FileInputStream(uploadFile); + + Connection.KeyVal fileData = post.data("_file"); + assertNotNull(fileData); + fileData.value("check.html"); + fileData.inputStream(stream); + + Connection.Response res; + try { + res = post.execute(); + } finally { + stream.close(); + } + + Document doc = res.parse(); + assertEquals(ihVal("Method", doc), "POST"); // from form action + assertEquals(ihVal("Part _file Filename", doc), "check.html"); + assertEquals(ihVal("Part _file Name", doc), "_file"); + assertEquals(ihVal("_function", doc), "tidy"); + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index b9a6deace8..0a1b243e56 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -324,33 +324,6 @@ public void baseHrefCorrectAfterHttpEquiv() throws IOException { assertEquals("http://example.com/foo.jpg", doc.select("img").first().absUrl("src")); } - /** - * Test fetching a form, and submitting it with a file attached. - */ - @Test - public void postHtmlFile() throws IOException { - Document index = Jsoup.connect("http://direct.infohound.net/tidy/").get(); - FormElement form = index.select("[name=tidy]").forms().get(0); - Connection post = form.submit(); - - File uploadFile = ParseTest.getFile("/htmltests/google-ipod.html"); - FileInputStream stream = new FileInputStream(uploadFile); - - Connection.KeyVal fileData = post.data("_file"); - fileData.value("check.html"); - fileData.inputStream(stream); - - Connection.Response res; - try { - res = post.execute(); - } finally { - stream.close(); - } - - Document out = res.parse(); - assertTrue(out.text().contains("HTML Tidy Complete")); - } - @Test public void handles201Created() throws IOException { Document doc = Jsoup.connect("http://direct.infohound.net/tools/201.pl").get(); // 201, location=jsoup diff --git a/src/test/resources/htmltests/upload-form.html b/src/test/resources/htmltests/upload-form.html new file mode 100644 index 0000000000..96434eb5ce --- /dev/null +++ b/src/test/resources/htmltests/upload-form.html @@ -0,0 +1,16 @@ +<html> +<head> + <title>Upload Form Test</title> + <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> +<body> +<h1>Upload Form Test</h1> + +<form action="/EchoServlet" method="POST" enctype="multipart/form-data" name="tidy" onsubmit="return verifySubmit()"> + <input type="hidden" name="_function" value="tidy"> + <label for="_file">Upload:</label> + <input name="_file" id="_file" type="file" size="40"> + <input type="submit"> +</form> + +</body> +</html> From 60bf584667ab1e3cb03ad02209b3b9006bb58519 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 28 Dec 2020 14:03:51 +1100 Subject: [PATCH 482/774] Reimplemented some URL fetch tests to use local Jetty server --- .../org/jsoup/integration/ConnectTest.java | 105 ++ .../org/jsoup/integration/UrlConnectTest.java | 89 -- .../integration/servlets/EchoServlet.java | 4 + .../integration/servlets/FileServlet.java | 1 - .../resources/htmltests/charset-base.html | 10 + src/test/resources/htmltests/large.html | 1004 +++++++++++++++++ 6 files changed, 1123 insertions(+), 90 deletions(-) create mode 100644 src/test/resources/htmltests/charset-base.html create mode 100644 src/test/resources/htmltests/large.html diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 01bf601842..f11eef5524 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -3,10 +3,15 @@ import org.jsoup.Connection; import org.jsoup.HttpStatusException; import org.jsoup.Jsoup; +import org.jsoup.helper.W3CDom; import org.jsoup.integration.servlets.*; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.FormElement; +import org.jsoup.parser.HtmlTreeBuilder; +import org.jsoup.parser.Parser; +import org.jsoup.parser.XmlTreeBuilder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -478,6 +483,7 @@ public void testBinaryContentTypeThrowsException() { assertEquals("", doc.title()); // the document title is unset, this tag is channel>title, not html>head>title assertEquals(3, doc.select("link").size()); assertEquals("application/rss+xml", con.response().contentType()); + assertTrue(doc.parser().getTreeBuilder() instanceof XmlTreeBuilder); assertEquals(Document.OutputSettings.Syntax.xml, doc.outputSettings().syntax()); } @@ -541,4 +547,103 @@ public void postHtmlFile() throws IOException { assertEquals(ihVal("Part _file Name", doc), "_file"); assertEquals(ihVal("_function", doc), "tidy"); } + + @Test + public void fetchHandlesXml() throws IOException { + String[] types = {"text/xml", "application/xml", "application/rss+xml", "application/xhtml+xml"}; + for (String type : types) { + fetchHandlesXml(type); + } + } + + void fetchHandlesXml(String contentType) throws IOException { + // should auto-detect xml and use XML parser, unless explicitly requested the html parser + String xmlUrl = FileServlet.urlTo("/htmltests/xml-test.xml"); + Connection con = Jsoup.connect(xmlUrl); + con.data(FileServlet.ContentTypeParam, contentType); + Document doc = con.get(); + Connection.Request req = con.request(); + assertTrue(req.parser().getTreeBuilder() instanceof XmlTreeBuilder); + assertEquals("<doc><val>One<val>Two</val>Three</val></doc>\n", doc.outerHtml()); + assertEquals(con.response().contentType(), contentType); + } + + @Test + public void fetchHandlesXmlAsHtmlWhenParserSet() throws IOException { + // should auto-detect xml and use XML parser, unless explicitly requested the html parser + String xmlUrl = FileServlet.urlTo("/htmltests/xml-test.xml"); + Connection con = Jsoup.connect(xmlUrl).parser(Parser.htmlParser()); + con.data(FileServlet.ContentTypeParam, "application/xml"); + Document doc = con.get(); + Connection.Request req = con.request(); + assertTrue(req.parser().getTreeBuilder() instanceof HtmlTreeBuilder); + assertEquals("<html> <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <doc> <val> One <val> Two </val>Three </val> </doc> </body> </html>", StringUtil.normaliseWhitespace(doc.outerHtml())); + } + + @Test + public void combinesSameHeadersWithComma() throws IOException { + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + Connection con = Jsoup.connect(echoUrl); + con.get(); + + Connection.Response res = con.response(); + assertEquals("text/html;charset=utf-8", res.header("Content-Type")); + assertEquals("no-cache, no-store", res.header("Cache-Control")); + + List<String> header = res.headers("Cache-Control"); + assertEquals(2, header.size()); + assertEquals("no-cache", header.get(0)); + assertEquals("no-store", header.get(1)); + } + + @Test + public void sendHeadRequest() throws IOException { + String url = FileServlet.urlTo("/htmltests/xml-test.xml"); + Connection con = Jsoup.connect(url) + .method(Connection.Method.HEAD) + .data(FileServlet.ContentTypeParam, "text/xml"); + final Connection.Response response = con.execute(); + assertEquals("text/xml", response.header("Content-Type")); + assertEquals("", response.body()); // head ought to have no body + Document doc = response.parse(); + assertEquals("", doc.text()); + } + + @Test + public void fetchToW3c() throws IOException { + String url = FileServlet.urlTo("/htmltests/upload-form.html"); + Document doc = Jsoup.connect(url).get(); + + W3CDom dom = new W3CDom(); + org.w3c.dom.Document wDoc = dom.fromJsoup(doc); + assertEquals(url, wDoc.getDocumentURI()); + String html = dom.asString(wDoc); + assertTrue(html.contains("Upload")); + } + + @Test + public void baseHrefCorrectAfterHttpEquiv() throws IOException { + // https://github.com/jhy/jsoup/issues/440 + Connection.Response res = Jsoup.connect(FileServlet.urlTo("/htmltests/charset-base.html")).execute(); + Document doc = res.parse(); + assertEquals("http://example.com/foo.jpg", doc.select("img").first().absUrl("src")); + } + + @Test + public void maxBodySize() throws IOException { + String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K + + Connection.Response defaultRes = Jsoup.connect(url).execute(); + Connection.Response smallRes = Jsoup.connect(url).maxBodySize(50 * 1024).execute(); // crops + Connection.Response mediumRes = Jsoup.connect(url).maxBodySize(200 * 1024).execute(); // crops + Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop + Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute(); + + int actualDocText = 269541; + assertEquals(actualDocText, defaultRes.parse().text().length()); + assertEquals(49165, smallRes.parse().text().length()); + assertEquals(196577, mediumRes.parse().text().length()); + assertEquals(actualDocText, largeRes.parse().text().length()); + assertEquals(actualDocText, unlimitedRes.parse().text().length()); + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 0a1b243e56..1db78c4b2f 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -231,24 +231,6 @@ public void handlesDodgyCharset() throws IOException { assertEquals("UTF-8", res.charset()); // set from default on parse } - @Test - public void maxBodySize() throws IOException { - String url = "http://direct.infohound.net/tools/large.html"; // 280 K - - Connection.Response defaultRes = Jsoup.connect(url).execute(); - Connection.Response smallRes = Jsoup.connect(url).maxBodySize(50 * 1024).execute(); // crops - Connection.Response mediumRes = Jsoup.connect(url).maxBodySize(200 * 1024).execute(); // crops - Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop - Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute(); - - int actualDocText = 269541; - assertEquals(actualDocText, defaultRes.parse().text().length()); - assertEquals(49165, smallRes.parse().text().length()); - assertEquals(196577, mediumRes.parse().text().length()); - assertEquals(actualDocText, largeRes.parse().text().length()); - assertEquals(actualDocText, unlimitedRes.parse().text().length()); - } - /** * Verify that security disabling feature works properly. * <p/> @@ -316,83 +298,12 @@ public void shouldWorkForDuplicateCharsetInTag() throws IOException { assertEquals("ISO-8859-1", res.charset()); } - @Test - public void baseHrefCorrectAfterHttpEquiv() throws IOException { - // https://github.com/jhy/jsoup/issues/440 - Connection.Response res = Jsoup.connect("http://direct.infohound.net/tools/charset-base.html").execute(); - Document doc = res.parse(); - assertEquals("http://example.com/foo.jpg", doc.select("img").first().absUrl("src")); - } - @Test public void handles201Created() throws IOException { Document doc = Jsoup.connect("http://direct.infohound.net/tools/201.pl").get(); // 201, location=jsoup assertEquals("https://jsoup.org/", doc.location()); } - @Test - public void fetchToW3c() throws IOException { - String url = "https://jsoup.org"; - Document doc = Jsoup.connect(url).get(); - - W3CDom dom = new W3CDom(); - org.w3c.dom.Document wDoc = dom.fromJsoup(doc); - assertEquals(url, wDoc.getDocumentURI()); - String html = dom.asString(wDoc); - assertTrue(html.contains("jsoup")); - } - - @Test - public void fetchHandlesXml() throws IOException { - // should auto-detect xml and use XML parser, unless explicitly requested the html parser - String xmlUrl = "http://direct.infohound.net/tools/parse-xml.xml"; - Connection con = Jsoup.connect(xmlUrl); - Document doc = con.get(); - Connection.Request req = con.request(); - assertTrue(req.parser().getTreeBuilder() instanceof XmlTreeBuilder); - assertEquals("<xml> <link> one </link> <table> Two </table> </xml>", StringUtil.normaliseWhitespace(doc.outerHtml())); - } - - @Test - public void fetchHandlesXmlAsHtmlWhenParserSet() throws IOException { - // should auto-detect xml and use XML parser, unless explicitly requested the html parser - String xmlUrl = "http://direct.infohound.net/tools/parse-xml.xml"; - Connection con = Jsoup.connect(xmlUrl).parser(Parser.htmlParser()); - Document doc = con.get(); - Connection.Request req = con.request(); - assertTrue(req.parser().getTreeBuilder() instanceof HtmlTreeBuilder); - assertEquals("<html> <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <xml> <link>one <table> Two </table> </xml> </body> </html>", StringUtil.normaliseWhitespace(doc.outerHtml())); - } - - @Test - public void combinesSameHeadersWithComma() throws IOException { - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - String url = "http://direct.infohound.net/tools/q.pl"; - Connection con = Jsoup.connect(url); - con.get(); - - Connection.Response res = con.response(); - assertEquals("text/html", res.header("Content-Type")); - assertEquals("no-cache, no-store", res.header("Cache-Control")); - - List<String> header = res.headers("Cache-Control"); - assertEquals(2, header.size()); - assertEquals("no-cache", header.get(0)); - assertEquals("no-store", header.get(1)); - } - - @Test - public void sendHeadRequest() throws IOException { - String url = "http://direct.infohound.net/tools/parse-xml.xml"; - Connection con = Jsoup.connect(url).method(Connection.Method.HEAD); - final Connection.Response response = con.execute(); - assertEquals("text/xml", response.header("Content-Type")); - assertEquals("", response.body()); // head ought to have no body - Document doc = response.parse(); - assertEquals("", doc.text()); - } - - /* Proxy tests. Assumes local proxy running on 8888, without system propery set (so that specifying it is required). */ diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java index a12272a6b7..defb6e60a3 100644 --- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -49,6 +49,10 @@ private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOExce res.setContentType(TextHtml); res.setStatus(intCode); + // no-cache headers for test + res.addHeader("Cache-Control", "no-cache"); + res.addHeader("Cache-Control", "no-store"); + PrintWriter w = res.getWriter(); w.write("<title>Webserver Environment Variables</title>\n" + diff --git a/src/test/java/org/jsoup/integration/servlets/FileServlet.java b/src/test/java/org/jsoup/integration/servlets/FileServlet.java index 6249232ec7..06ed2149d8 100644 --- a/src/test/java/org/jsoup/integration/servlets/FileServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/FileServlet.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.nio.file.Files; - public class FileServlet extends BaseServlet { public static final String Url = TestServer.map(FileServlet.class); public static final String ContentTypeParam = "contentType"; diff --git a/src/test/resources/htmltests/charset-base.html b/src/test/resources/htmltests/charset-base.html new file mode 100644 index 0000000000..3ef6e54cf2 --- /dev/null +++ b/src/test/resources/htmltests/charset-base.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <base href="http://example.com" /> + <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> +<body> +<img src="/foo.jpg" /> +</body> +</html> diff --git a/src/test/resources/htmltests/large.html b/src/test/resources/htmltests/large.html new file mode 100644 index 0000000000..797afecbff --- /dev/null +++ b/src/test/resources/htmltests/large.html @@ -0,0 +1,1004 @@ +<html><head><title>Large HTML</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> + +<body> +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p> + +<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + +<p><i>Proin sodales libero eget ante</i>. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p>Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Suspendisse potenti</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + +<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p><b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Ut eu diam at pede suscipit sodales</b>. Nulla quis sem at nibh elementum imperdiet. <b>Maecenas aliquet mollis lectus</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Nulla ut felis in purus aliquam imperdiet</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Duis sagittis ipsum</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Curabitur tortor</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Ut fringilla</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p> + +<p>Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. Cras metus. </p> + +<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Nulla quam</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Sed cursus ante dapibus diam</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. <b>Mauris massa</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Sed dignissim lacinia nunc</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. <i>Maecenas mattis</i>. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nam nec ante</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Suspendisse potenti</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Vestibulum sapien</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Proin sodales libero eget ante</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p> + +<p>Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p> + +<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Sed nisi</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p> + +<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p> + +<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p> + +<p>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Nulla quis sem at nibh elementum imperdiet. </p> + +<p><b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. </p> + +<p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p><b>Maecenas mattis</b>. Nulla facilisi. Ut fringilla. <b>Mauris ipsum</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <i>Pellentesque nibh</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p><b>Vestibulum sapien</b>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Sed convallis tristique sem</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p> + +<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Vestibulum tincidunt malesuada tellus</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p><b>Morbi mi</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <i>Morbi mi</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Praesent libero</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Vestibulum lacinia arcu eget nulla</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p> + +<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Sed nisi</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p> + +<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Proin quam</b>. Praesent blandit dolor. <b>Proin quam</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. <b>Curabitur sit amet mauris</b>. Aenean laoreet. </p> + +<p>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p> + +<p><b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <i>Nulla ut felis in purus aliquam imperdiet</i>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p> + +<p>Aenean quam. <i>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</i>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur tortor</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p> + +<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Sed lectus</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Ut fringilla</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p> + +<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Proin sodales libero eget ante</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Fusce nec tellus sed augue semper porta</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. </p> + +<p><i>Fusce nec tellus sed augue semper porta</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Maecenas mattis</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p>Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Suspendisse potenti</b>. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Integer euismod lacus luctus magna</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. </p> + +<p>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. <b>Duis sagittis ipsum</b>. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. </p> + +<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Nam nec ante</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. <i>Sed convallis tristique sem</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <i>Sed non quam</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + +<p><b>Morbi in dui quis est pulvinar ullamcorper</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> + +<p>Integer nec odio. Praesent libero. <i>Integer lacinia sollicitudin massa</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Curabitur sodales ligula in libero</i>. Integer euismod lacus luctus magna. </p> + +<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p><i>Nunc feugiat mi a tellus consequat imperdiet</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Cras metus</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Sed pretium blandit orci</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p><b>Fusce ac turpis quis ligula lacinia aliquet</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Aenean quam</i>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Praesent blandit dolor</i>. Proin sodales libero eget ante. Nulla quam. </p> + +<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Praesent blandit dolor</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Maecenas aliquet mollis lectus</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Nulla quis sem at nibh elementum imperdiet</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <b>Curabitur sodales ligula in libero</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Curabitur sodales ligula in libero</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Curabitur sodales ligula in libero</i>. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Ut fringilla</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p> + +<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p><b>Cras metus</b>. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p> + +<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. Sed convallis tristique sem. <b>Sed dignissim lacinia nunc</b>. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur sodales ligula in libero</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Vestibulum sapien</b>. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Integer euismod lacus luctus magna</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>In vel mi sit amet augue congue elementum</b>. Sed aliquet risus a tortor. Integer id quam. <b>Ut ultrices ultrices enim</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p> + +<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <i>Ut eu diam at pede suscipit sodales</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur tortor</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Mauris massa</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent libero</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Etiam ultrices</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Proin sodales libero eget ante</b>. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Nulla quis sem at nibh elementum imperdiet. </p> + +<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Sed dignissim lacinia nunc</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Pellentesque nibh</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nulla facilisi. </p> + +<p>Ut fringilla. <i>Nulla quis sem at nibh elementum imperdiet</i>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nulla facilisi</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. <b>Suspendisse potenti</b>. Morbi in ipsum sit amet pede facilisis laoreet. <b>Etiam ultrices</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p><b>In vel mi sit amet augue congue elementum</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p><b>Praesent libero</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <i>Integer nec odio</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p> + +<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Maecenas mattis</b>. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Vestibulum tincidunt malesuada tellus</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <i>Integer lacinia sollicitudin massa</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed cursus ante dapibus diam</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. <b>Vestibulum lacinia arcu eget nulla</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p> + +<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed cursus ante dapibus diam</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p>Vestibulum sapien. Proin quam. Etiam ultrices. <i>Mauris ipsum</i>. Suspendisse in justo eu magna luctus suscipit. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Sed lectus. <b>Suspendisse potenti</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. <i>Suspendisse potenti</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p> + +<p>Proin sodales libero eget ante. Nulla quam. <b>Cras metus</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Sed pretium blandit orci</i>. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. <b>Duis sagittis ipsum</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Curabitur sodales ligula in libero</b>. Mauris ipsum. </p> + +<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Duis sagittis ipsum</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. </p> + +<p>Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p> + +<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Vestibulum tincidunt malesuada tellus</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <i>Integer euismod lacus luctus magna</i>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. </p> + +<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <i>Nulla quam</i>. Curabitur tortor. </p> + +<p><b>Praesent libero</b>. Pellentesque nibh. Aenean quam. <b>Mauris massa</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p> + +<p><b>Pellentesque nibh</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p> + +<p>Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Vestibulum sapien</i>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. </p> + +<p>Vestibulum lacinia arcu eget nulla. <b>Maecenas aliquet mollis lectus</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Duis sagittis ipsum</b>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. <b>Vestibulum lacinia arcu eget nulla</b>. Proin ut ligula vel nunc egestas porttitor. </p> + +<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Curabitur sodales ligula in libero</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Curabitur sodales ligula in libero</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Ut fringilla</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Suspendisse potenti</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed lectus</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p> + +<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Nulla facilisi</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. <i>Curabitur sit amet mauris</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. <b>Sed cursus ante dapibus diam</b>. Curabitur tortor. <b>Sed cursus ante dapibus diam</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. </p> + +<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p> + +<p><b>Integer lacinia sollicitudin massa</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Praesent libero. Sed cursus ante dapibus diam. </p> + +<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <i>Ut eu diam at pede suscipit sodales</i>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p> + +<p>Aenean quam. In scelerisque sem at dolor. <b>Vestibulum lacinia arcu eget nulla</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur sodales ligula in libero</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Curabitur sodales ligula in libero</i>. Nam nec ante. </p> + +<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Pellentesque nibh</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p> + +<p>Praesent blandit dolor. <i>Nulla facilisi</i>. Sed non quam. In vel mi sit amet augue congue elementum. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p> + +<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Nulla facilisi</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Sed non quam</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <b>Mauris massa</b>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <i>Nulla ut felis in purus aliquam imperdiet</i>. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Mauris massa</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Ut fringilla</i>. Integer lacinia sollicitudin massa. Cras metus. <i>Vestibulum sapien</i>. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p> + +<p>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Ut ultrices ultrices enim</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Proin sodales libero eget ante</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p> + +<p>Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Fusce nec tellus sed augue semper porta. <i>Ut eu diam at pede suscipit sodales</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Sed nisi</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. </p> + +<p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Nam nec ante</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p> + +<p>Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Sed cursus ante dapibus diam. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed nisi. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed cursus ante dapibus diam</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p> + +<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Mauris ipsum</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p> + +<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Sed dignissim lacinia nunc. Curabitur tortor. <b>Duis sagittis ipsum</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <i>Curabitur sodales ligula in libero</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p><b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p> + +<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Suspendisse in justo eu magna luctus suscipit</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Cras metus</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Duis sagittis ipsum</b>. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p> + +<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nam nec ante</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p> + +<p>Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. <i>Sed lectus</i>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Sed non quam</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <b>Morbi mi</b>. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <i>Integer lacinia sollicitudin massa</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. </p> + +<p>Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Fusce nec tellus sed augue semper porta</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p> + +<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Duis sagittis ipsum</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <i>Quisque volutpat condimentum velit</i>. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <b>Curabitur sit amet mauris</b>. Nulla quam. </p> + +<p><b>Curabitur sit amet mauris</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Sed non quam</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Curabitur sit amet mauris</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p> + +<p>Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p>Curabitur tortor. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Pellentesque nibh</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p> + +<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <i>Pellentesque nibh</i>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Mauris ipsum</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Morbi in dui quis est pulvinar ullamcorper. <b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. <i>Vestibulum sapien</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. <b>Ut ultrices ultrices enim</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Morbi mi</b>. Ut eu diam at pede suscipit sodales. <b>Proin sodales libero eget ante</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Proin sodales libero eget ante</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Vestibulum lacinia arcu eget nulla</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <b>Maecenas mattis</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Aenean quam</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Mauris ipsum</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. </p> + +<p>Integer lacinia sollicitudin massa. Cras metus. <b>Sed non quam</b>. Sed aliquet risus a tortor. Integer id quam. <i>Ut fringilla</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Sed aliquet risus a tortor</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Sed non quam</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Sed pretium blandit orci</b>. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. <i>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</i>. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. <b>Duis sagittis ipsum</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>In scelerisque sem at dolor</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Suspendisse potenti</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. </p> + +<p>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Praesent blandit dolor</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> + +<p>Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. <b>Praesent libero</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <i>Nulla ut felis in purus aliquam imperdiet</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Duis sagittis ipsum</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <i>Pellentesque nibh</i>. Proin quam. Etiam ultrices. <i>Curabitur tortor</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p> + +<p>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <i>Curabitur sit amet mauris</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Nulla quam</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Sed aliquet risus a tortor</i>. Integer nec odio. Praesent libero. <i>Sed aliquet risus a tortor</i>. Sed cursus ante dapibus diam. Sed nisi. </p> + +<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Sed cursus ante dapibus diam</i>. Fusce ac turpis quis ligula lacinia aliquet. <b>Sed dignissim lacinia nunc</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed cursus ante dapibus diam</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p> + +<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed lectus</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. <i>Praesent blandit dolor</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Nulla facilisi</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Nulla ut felis in purus aliquam imperdiet</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p><b>Duis sagittis ipsum</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. <i>Praesent mauris</i>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p> + +<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Ut fringilla</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. <b>Praesent blandit dolor</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p><b>Cras metus</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p> + +<p><i>Cras metus</i>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Integer nec odio</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Nulla quis sem at nibh elementum imperdiet</b>. Pellentesque nibh. <b>Sed cursus ante dapibus diam</b>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Aenean quam</b>. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Aenean quam</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Etiam ultrices</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + +<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Morbi mi</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Aenean quam. In scelerisque sem at dolor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent libero</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p><b>Curabitur sodales ligula in libero</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. </p> + +<p><b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Suspendisse in justo eu magna luctus suscipit</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p> + +<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Nulla facilisi</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Cras metus</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Proin sodales libero eget ante</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p> + +<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Praesent mauris</b>. Pellentesque nibh. </p> + +<p>Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Fusce nec tellus sed augue semper porta</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Pellentesque nibh</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p> + +<p>Sed non quam. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Sed lectus</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p> + +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <i>Ut ultrices ultrices enim</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p> + +<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Sed convallis tristique sem</i>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p> + +<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Mauris ipsum</i>. Vestibulum tincidunt malesuada tellus. <i>Ut fringilla</i>. Ut ultrices ultrices enim. <b>Integer euismod lacus luctus magna</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. </p> + +<p>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <b>Curabitur sit amet mauris</b>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Sed pretium blandit orci</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p><b>Maecenas mattis</b>. Quisque volutpat condimentum velit. <i>Sed dignissim lacinia nunc</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nam nec ante</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Nam nec ante</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p> + +<p><b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Sed aliquet risus a tortor</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p> + +<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <i>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</i>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Ut fringilla</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Ut fringilla</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + +<p>Aenean laoreet. <i>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Maecenas aliquet mollis lectus</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Duis sagittis ipsum</b>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p> + +<p>Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Proin ut ligula vel nunc egestas porttitor</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. Ut fringilla. <i>Nulla quis sem at nibh elementum imperdiet</i>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p> + +<p>Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Integer lacinia sollicitudin massa</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p> + +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Proin sodales libero eget ante</i>. Integer nec odio. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Ut eu diam at pede suscipit sodales</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p><b>Integer nec odio</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Praesent libero</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p><i>Curabitur tortor</i>. Nam nec ante. <i>Curabitur tortor</i>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Curabitur tortor</i>. Ut fringilla. Suspendisse potenti. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <i>Curabitur tortor</i>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Integer euismod lacus luctus magna</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Vestibulum tincidunt malesuada tellus</b>. Integer id quam. Morbi mi. <i>Sed non quam</i>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Sed aliquet risus a tortor</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Ut eu diam at pede suscipit sodales</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Curabitur tortor</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Vestibulum sapien</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Vestibulum sapien</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p><b>Ut ultrices ultrices enim</b>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p> + +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Ut eu diam at pede suscipit sodales</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Sed pretium blandit orci</i>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. <b>Duis sagittis ipsum</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. </p> + +<p><i>Duis sagittis ipsum</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <b>Sed convallis tristique sem</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Morbi in ipsum sit amet pede facilisis laoreet. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p><b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <i>Praesent blandit dolor</i>. Nulla quam. Aenean laoreet. <b>Curabitur sit amet mauris</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Integer id quam</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed cursus ante dapibus diam. </p> + +<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Sed cursus ante dapibus diam</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <b>Mauris ipsum</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <i>Pellentesque nibh</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Suspendisse potenti</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Mauris ipsum</i>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <b>Ut ultrices ultrices enim</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</i>. Integer nec odio. Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. <i>Aenean laoreet</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p> + +<p><i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Ut eu diam at pede suscipit sodales</i>. Aenean quam. </p> + +<p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Aenean quam</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p> + +<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p> + +<p><b>Ut fringilla</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. <i>Sed lectus</i>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Maecenas aliquet mollis lectus</b>. Nulla quis sem at nibh elementum imperdiet. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Duis sagittis ipsum. <i>Ut eu diam at pede suscipit sodales</i>. Praesent mauris. </p> + +<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Nulla quis sem at nibh elementum imperdiet</b>. Sed dignissim lacinia nunc. Curabitur tortor. <i>Nulla ut felis in purus aliquam imperdiet</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. <b>Praesent mauris</b>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. <i>Proin ut ligula vel nunc egestas porttitor</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Vestibulum sapien</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Sed lectus</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Integer id quam</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. <i>Sed aliquet risus a tortor</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Vivamus consectetuer risus et tortor</b>. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Vestibulum lacinia arcu eget nulla</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. </p> + +<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. Etiam ultrices. </p> + +<p>Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. </p> + +<p>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Morbi mi</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Mauris massa</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Curabitur sodales ligula in libero</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Quisque volutpat condimentum velit</b>. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <i>Integer lacinia sollicitudin massa</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Maecenas aliquet mollis lectus</b>. Duis sagittis ipsum. Praesent mauris. <i>Nulla quam</i>. Fusce nec tellus sed augue semper porta. Mauris massa. <i>Aenean laoreet</i>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Fusce nec tellus sed augue semper porta</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p> + +<p>Morbi in dui quis est pulvinar ullamcorper. <b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. Integer lacinia sollicitudin massa. <i>Etiam ultrices</i>. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Mauris massa</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Aenean quam</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p> + +<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <i>Proin quam</i>. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Proin sodales libero eget ante</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p> + +<p><b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Praesent libero. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed cursus ante dapibus diam. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Sed cursus ante dapibus diam</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Curabitur sodales ligula in libero</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <i>Vestibulum sapien</i>. Nulla facilisi. </p> + +<p>Integer lacinia sollicitudin massa. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Sed aliquet risus a tortor</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <i>Cras metus</i>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. </p> + +<p><b>Nulla ut felis in purus aliquam imperdiet</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed cursus ante dapibus diam</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Mauris massa</i>. Fusce ac turpis quis ligula lacinia aliquet. <i>Sed cursus ante dapibus diam</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. </p> + +<p>Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Mauris ipsum</b>. Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Vestibulum sapien</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p> + +<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Morbi mi</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p> + +<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Praesent libero</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Nulla quis sem at nibh elementum imperdiet</b>. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. <i>Nulla ut felis in purus aliquam imperdiet</i>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p><b>Curabitur tortor</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Maecenas mattis</i>. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Ut ultrices ultrices enim. <i>Suspendisse potenti</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Ut ultrices ultrices enim</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p> + +<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Integer nec odio</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. <b>Sed dignissim lacinia nunc</b>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Nulla quis sem at nibh elementum imperdiet</i>. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Sed aliquet risus a tortor. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p> + +<p><i>Sed aliquet risus a tortor</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Curabitur sit amet mauris</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Proin sodales libero eget ante</i>. Duis sagittis ipsum. Praesent mauris. </p> + +<p><b>Nulla ut felis in purus aliquam imperdiet</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed pretium blandit orci</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Vestibulum lacinia arcu eget nulla</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Suspendisse in justo eu magna luctus suscipit</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p> + +<p>Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <i>Duis sagittis ipsum</i>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Curabitur sodales ligula in libero</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p> + +<p>Praesent blandit dolor. <i>In scelerisque sem at dolor</i>. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Nam nec ante</i>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>In vel mi sit amet augue congue elementum</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p> + +<p><b>Aenean laoreet</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. <b>Praesent libero</b>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Praesent libero</i>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent libero</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Pellentesque nibh</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Curabitur tortor</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Integer euismod lacus luctus magna. </p> + +<p><b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Ut fringilla</i>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. </p> + +<p>Cras metus. Sed aliquet risus a tortor. <b>Vestibulum tincidunt malesuada tellus</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <b>Integer id quam</b>. Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Sed pretium blandit orci</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p> + +<p>Fusce nec tellus sed augue semper porta. <i>Sed pretium blandit orci</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Maecenas aliquet mollis lectus</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Duis sagittis ipsum</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Sed convallis tristique sem</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p> + +<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Vestibulum sapien</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p> + +<p><b>Proin sodales libero eget ante</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Proin sodales libero eget ante</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p>Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. <i>Sed nisi</i>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <i>Maecenas mattis</i>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <b>Nulla facilisi</b>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p><b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Suspendisse in justo eu magna luctus suscipit</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. <b>Vestibulum tincidunt malesuada tellus</b>. Aenean laoreet. <i>Suspendisse in justo eu magna luctus suscipit</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. </p> + +<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent mauris</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Pellentesque nibh</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. </p> + +<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p> + +<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Vestibulum sapien</i>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p> + +<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Integer id quam</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Vivamus consectetuer risus et tortor. </p> + +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Praesent libero</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Duis sagittis ipsum</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p><b>Maecenas mattis</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Mauris ipsum</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Nam nec ante</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p><b>Integer euismod lacus luctus magna</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p><i>Integer euismod lacus luctus magna</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. <b>Sed dignissim lacinia nunc</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Sed dignissim lacinia nunc</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p>Vestibulum sapien. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Etiam ultrices</b>. Sed non quam. <b>Vestibulum sapien</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>In vel mi sit amet augue congue elementum</b>. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + +<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p> + +<p><b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. <i>Ut ultrices ultrices enim</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Integer nec odio</b>. Fusce nec tellus sed augue semper porta. <i>Sed aliquet risus a tortor</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Sed nisi</b>. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. <b>Sed nisi</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Curabitur sodales ligula in libero</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Pellentesque nibh</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <i>Ut fringilla</i>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + +<p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Nulla facilisi</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p><b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p> + +<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <b>Sed dignissim lacinia nunc</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p> + +<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Ut fringilla</b>. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Ut fringilla</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p> + +<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Ut fringilla</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <i>Etiam ultrices</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <i>Integer id quam</i>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Curabitur tortor</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Ut fringilla. </p> + +<p><b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Ut fringilla</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p> + +<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Etiam ultrices</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Aenean laoreet</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Sed aliquet risus a tortor</i>. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. <b>Nulla ut felis in purus aliquam imperdiet</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <i>Nulla ut felis in purus aliquam imperdiet</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Nulla quis sem at nibh elementum imperdiet</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Mauris massa</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <b>Suspendisse potenti</b>. Morbi in ipsum sit amet pede facilisis laoreet. <b>Vestibulum sapien</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Sed lectus</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p> + +<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Maecenas aliquet mollis lectus</b>. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. </p> + +<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p>Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. <b>Suspendisse in justo eu magna luctus suscipit</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. <i>Integer euismod lacus luctus magna</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> + +<p>Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. <b>Vivamus consectetuer risus et tortor</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <i>Nulla ut felis in purus aliquam imperdiet</i>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p>Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed dignissim lacinia nunc</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Mauris ipsum</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. </p> + +<p>Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Proin sodales libero eget ante</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Praesent libero</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p><i>Duis sagittis ipsum</i>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p> + +<p>Suspendisse potenti. <b>Sed convallis tristique sem</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <i>Proin quam</i>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p> + +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p>Curabitur tortor. <b>Fusce nec tellus sed augue semper porta</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Sed dignissim lacinia nunc</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Nulla facilisi</b>. Sed lectus. Integer euismod lacus luctus magna. </p> + +<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Sed lectus</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p><b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Praesent blandit dolor</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Nulla facilisi</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p> + +<p><i>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Integer id quam</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Sed pretium blandit orci</b>. Sed nisi. </p> + +<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Maecenas aliquet mollis lectus</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <b>Mauris massa</b>. Aenean quam. </p> + +<p>In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Fusce nec tellus sed augue semper porta</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p> + +<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p> + +<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Nam nec ante</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p> + +<p>Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. </p> + +<p>Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Sed cursus ante dapibus diam</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Nam nec ante</b>. Etiam ultrices. </p> + +<p>Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <i>Mauris ipsum</i>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Mauris ipsum</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Proin sodales libero eget ante. Nulla quam. <b>Cras metus</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p><b>Sed aliquet risus a tortor</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Aenean laoreet</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p> + +<p><b>Ut eu diam at pede suscipit sodales</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Sed cursus ante dapibus diam</i>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p><b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Nulla facilisi</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. </p> + +<p>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <i>Sed non quam</i>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. </p> + +<p>Sed nisi. <i>Integer id quam</i>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Vivamus consectetuer risus et tortor</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. <b>Duis sagittis ipsum</b>. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>In scelerisque sem at dolor</b>. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Vestibulum lacinia arcu eget nulla</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Ut ultrices ultrices enim</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p> + +<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p> + +<p><b>Vivamus consectetuer risus et tortor</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p><i>Duis sagittis ipsum</i>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <i>Mauris massa</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Sed convallis tristique sem</i>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Proin quam</i>. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + +<p>Aenean laoreet. <b>Curabitur sit amet mauris</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Integer lacinia sollicitudin massa</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p> + +<p>Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. Ut fringilla. <b>Mauris ipsum</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p> + +<p><b>Etiam ultrices</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Sed non quam</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p> + +<p><b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p> + +<p><b>Morbi mi</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Ut eu diam at pede suscipit sodales</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Fusce nec tellus sed augue semper porta</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p> + +<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p> + +<p>Etiam ultrices. <b>Nulla facilisi</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. <b>Cras metus</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p> + +<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Vivamus consectetuer risus et tortor</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p> + +<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Nulla quis sem at nibh elementum imperdiet</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p> + +<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Ut fringilla</b>. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Sed lectus</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p> + +<p><b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p> + +<p>Integer nec odio. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + +<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. <b>Vestibulum lacinia arcu eget nulla</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Vestibulum lacinia arcu eget nulla</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + +<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <b>Maecenas mattis</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Suspendisse potenti</b>. Integer euismod lacus luctus magna. </p> + +<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Cras metus. </p> + +<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + +<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Proin sodales libero eget ante</i>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p> + +<p>Mauris massa. <b>Vivamus consectetuer risus et tortor</b>. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Duis sagittis ipsum</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p> + +<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Maecenas mattis</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed cursus ante dapibus diam</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p> + +<p><i>Aenean quam</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Mauris ipsum</i>. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. <i>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</i>. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Integer id quam</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Duis sagittis ipsum</b>. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Curabitur tortor</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p> + +<p>Nulla facilisi. <i>Duis sagittis ipsum</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>In scelerisque sem at dolor</i>. Sed non quam. </p> + +<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p> + +<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <b>Integer lacinia sollicitudin massa</b>. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p> + +<p>Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Sed pretium blandit orci</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Sed pretium blandit orci</b>. Duis sagittis ipsum. Praesent mauris. </p> + +<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. </p> + +<p>Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <i>Sed convallis tristique sem</i>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p> + +<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Vestibulum sapien</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p> + +<p><i>Mauris ipsum</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Vestibulum sapien</i>. Integer lacinia sollicitudin massa. Cras metus. <b>In vel mi sit amet augue congue elementum</b>. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p> + +<p>Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Integer lacinia sollicitudin massa</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p> + +<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Praesent mauris</b>. Curabitur tortor. Pellentesque nibh. </p> + +<p>Aenean quam. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. </p> + +<p>Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p> + +<p>Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <i>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</i>. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. </p> + +<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <i>Suspendisse in justo eu magna luctus suscipit</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p> + +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <i>Integer id quam</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <i>Ut eu diam at pede suscipit sodales</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p> + +<p>Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Etiam ultrices</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p> + +<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Proin sodales libero eget ante</i>. Duis sagittis ipsum. </p> + +<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + +<p>Sed convallis tristique sem. <b>Praesent mauris</b>. Proin ut ligula vel nunc egestas porttitor. <i>Duis sagittis ipsum</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + +<p><b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p> + +<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <i>Proin quam</i>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Nulla quam. </p> + +<p><b>Vestibulum tincidunt malesuada tellus</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Praesent blandit dolor</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Integer lacinia sollicitudin massa</i>. Integer nec odio. </p> + +<p>Praesent libero. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p> + +<p>Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Curabitur tortor</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> + +<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <i>In scelerisque sem at dolor</i>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p> + +<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Suspendisse in justo eu magna luctus suscipit</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. </p> + +<p>Cras metus. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Sed aliquet risus a tortor. Integer id quam. <b>Sed non quam</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <b>Ut ultrices ultrices enim</b>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Sed aliquet risus a tortor</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p> + +<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Ut ultrices ultrices enim</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p> + +<p><b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p> + +<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <i>Fusce nec tellus sed augue semper porta</i>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p> + +<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Curabitur sodales ligula in libero</i>. Integer euismod lacus luctus magna. <i>Maecenas mattis</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Nam nec ante</i>. Vestibulum tincidunt malesuada tellus. </p> + +<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>Vestibulum tincidunt malesuada tellus</b>. Sed aliquet risus a tortor. Integer id quam. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Integer lacinia sollicitudin massa</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. <b>Nulla quam</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + +<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Nulla ut felis in purus aliquam imperdiet</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p> + +<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <b>Curabitur sodales ligula in libero</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p> + +<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p> + +<p><b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Sed convallis tristique sem</i>. Praesent blandit dolor. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Proin quam</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. </p> + +<p>Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p> + +<p>Nulla ut felis in purus aliquam imperdiet. <b>Aenean laoreet</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Integer lacinia sollicitudin massa</i>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. </p> + +<p><b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Duis sagittis ipsum</b>. Maecenas mattis. Sed convallis tristique sem. <b>Sed dignissim lacinia nunc</b>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p> + +<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <i>Curabitur sodales ligula in libero</i>. Nulla facilisi. <i>Vestibulum lacinia arcu eget nulla</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. </p> + +<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + +<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Proin quam</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> + +</body></html> From af763d07c41085fcf518e547857d419ffb455d52 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 28 Dec 2020 14:37:08 +1100 Subject: [PATCH 483/774] Document#body should return the framset el for frameset docs --- CHANGES | 3 ++- src/main/java/org/jsoup/nodes/Document.java | 11 ++++++----- src/test/java/org/jsoup/nodes/DocumentTest.java | 12 +++++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index d9dbf8f366..8cd1f1d43e 100644 --- a/CHANGES +++ b/CHANGES @@ -21,7 +21,8 @@ jsoup changelog * Improvement: added Element#id(String) ID attribute setter. * Improvement: in Document, #body() and #head() accessors will now automatically create those elements, if they were - missing (e.g. if the Document was not parsed from HTML). + missing (e.g. if the Document was not parsed from HTML). Additionally, the #body() method returns the frameset + element (instead of null) for frameset documents. * Improvement: when cleaning a document, the output settings of the original document are cloned into the cleaned document. diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index a8ddad8643..1929a2e95b 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -109,17 +109,18 @@ public Element head() { } /** - Get this document's {@code body} element. + Get this document's {@code <body>} or {@code <frameset>} element. <p> - As a side-effect, if this Document does not already have a HTML structure, it will be created. If you do not want - that, use {@code #selectFirst("body")} instead. + As a <b>side-effect</b>, if this Document does not already have a HTML structure, it will be created with a {@code + <body>} element. If you do not want that, use {@code #selectFirst("body")} instead. - @return {@code body} element. + @return {@code body} element for documents with a {@code <body>}, a new {@code <body>} element if the document + had no contents, or the outermost {@code <frameset> element} for frameset documents. */ public Element body() { Element html = htmlEl(); for (Element el: html.childElementsList()) { - if (el.normalName().equals("body")) + if ("body".equals(el.normalName()) || "frameset".equals(el.normalName())) return el; } return html.appendElement("body"); diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index aebaa9a1b5..97d4838522 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -497,16 +497,19 @@ public void testShiftJisRoundtrip() throws Exception { assertNotNull(head); assertEquals("Frame Test", doc.title()); - // Frameset docs per html5 spec have no body. + // Frameset docs per html5 spec have no body element - but instead a frameset elelemt assertNull(doc.selectFirst("body")); + Element frameset = doc.selectFirst("frameset"); + assertNotNull(frameset); - // but we want the .body() tag to not null; so we auto-normalize it + // the body() method returns body or frameset and does not otherwise modify the document // doing it in body() vs parse keeps the html close to original for round-trip option Element body = doc.body(); assertNotNull(body); - assertEquals("", body.html()); + assertSame(frameset, body); + assertEquals("frame", body.child(0).tagName()); - assertNotNull(doc.selectFirst("body")); + assertNull(doc.selectFirst("body")); // did not vivify a body element String expected = "<html>\n" + " <head>\n" + @@ -515,7 +518,6 @@ public void testShiftJisRoundtrip() throws Exception { " <frameset id=\"id\">\n" + " <frame src=\"foo.html\">\n" + " </frameset>\n" + - " <body></body>\n" + "</html>"; assertEquals(expected, doc.html()); } From 1c5071f150d20b17e47707c6db72e72fa299be15 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 28 Dec 2020 15:10:08 +1100 Subject: [PATCH 484/774] Removed obsolete method --- src/main/java/org/jsoup/nodes/Document.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 1929a2e95b..b6d3660f98 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -223,21 +223,6 @@ private void normaliseStructure(String tag, Element htmlEl) { } } - // fast method to get first by tag name, used for html, head, body finders - no recursive descent - private @Nullable Element findFirstElementByTagName(String tag) { - Element root = child(0); // the HTML element - Document sits above - if (root.nodeName().equals(tag)) - return this; - else { - int size = root.childNodeSize(); - for (int i = 0; i < size; i++) { - if (root.childNode(i).nodeName().equals(tag)) - return (Element) root.childNode(i); - } - } - return null; // todo - make this blow up in most cases - how to handle frameset (no body)?) - } - @Override public String outerHtml() { return super.html(); // no outer wrapper tag From c2ef8e9785d814fcb2b805df120a99036267ed81 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 28 Dec 2020 21:40:04 +1100 Subject: [PATCH 485/774] Simplified isContentForTagData Loop was showing up during profiling; not needed with just two options --- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index ddb9bd01a2..3009d08cdb 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -750,12 +750,7 @@ public String toString() { '}'; } - private static final String[] dataTags = {"script", "style"}; - protected boolean isContentForTagData(String normalName) { - for (String tag : dataTags) { - if (tag.equals(normalName)) - return true; - } - return false; + protected boolean isContentForTagData(final String normalName) { + return (normalName.equals("script") || normalName.equals("style")); } } From b482e0081bf51e4d6f94393fef4d1dc743fe41dd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 29 Dec 2020 11:05:54 +1100 Subject: [PATCH 486/774] Typo --- src/main/java/org/jsoup/parser/TreeBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index baa45037ec..197ce3b3bf 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -109,7 +109,7 @@ protected Element currentElement() { } /** - * If the parser is tracking errors, and an error at the current position. + * If the parser is tracking errors, add an error at the current position. * @param msg error message */ protected void error(String msg) { From 50576455fd4f244375d05b029c25b3b17edfbb74 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 29 Dec 2020 13:22:05 +1100 Subject: [PATCH 487/774] Tweaked new Attributes Was always hitting Array.copy from 0 to 2. Very marginal improvement as new String[] vs Array.copy from 0 is basically the same thing. Some improvement from using min size = 3, fewer resize copies. --- src/main/java/org/jsoup/nodes/Attributes.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 1d6e6904e5..b2dc18efb9 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -33,35 +33,35 @@ * @author Jonathan Hedley, jonathan@hedley.net */ public class Attributes implements Iterable<Attribute>, Cloneable { + // The Attributes object is only created on the first use of an attribute; the Element will just have a null + // Attribute slot otherwise protected static final String dataPrefix = "data-"; // Indicates a jsoup internal key. Can't be set via HTML. (It could be set via accessor, but not too worried about // that. Suppressed from list, iter. static final char InternalPrefix = '/'; - private static final int InitialCapacity = 2; // sampling found mean count when attrs present = 1.49; 1.08 overall. 2.6:1 have attrs. + private static final int InitialCapacity = 3; // sampling found mean count when attrs present = 1.49; 1.08 overall. 2.6:1 don't have any attrs. // manages the key/val arrays private static final int GrowthFactor = 2; - private static final String[] Empty = {}; static final int NotFound = -1; private static final String EmptyString = ""; - private int size = 0; // number of slots used (not capacity, which is keys.length - String[] keys = Empty; - String[] vals = Empty; + private int size = 0; // number of slots used (not total capacity, which is keys.length) + String[] keys = new String[InitialCapacity]; + String[] vals = new String[InitialCapacity]; // check there's room for more private void checkCapacity(int minNewSize) { Validate.isTrue(minNewSize >= size); - int curSize = keys.length; - if (curSize >= minNewSize) + int curCap = keys.length; + if (curCap >= minNewSize) return; + int newCap = curCap >= InitialCapacity ? size * GrowthFactor : InitialCapacity; + if (minNewSize > newCap) + newCap = minNewSize; - int newSize = curSize >= InitialCapacity ? size * GrowthFactor : InitialCapacity; - if (minNewSize > newSize) - newSize = minNewSize; - - keys = Arrays.copyOf(keys, newSize); - vals = Arrays.copyOf(vals, newSize); + keys = Arrays.copyOf(keys, newCap); + vals = Arrays.copyOf(vals, newCap); } int indexOfKey(String key) { From 1a0ba6a331a2776ae56d0111481f4b25e67bb1a5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 29 Dec 2020 14:37:44 +1100 Subject: [PATCH 488/774] Test for #1186 --- CHANGES | 2 +- src/test/java/org/jsoup/nodes/ElementTest.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8cd1f1d43e..f89f7013fd 100644 --- a/CHANGES +++ b/CHANGES @@ -56,7 +56,7 @@ jsoup changelog * Bugfix: when wrapping an element with HTML that included multiple sibling elements, those siblings were incorrectly added as children of the wrapper instead of siblings. - * Bugfix: when setting the content of a script or style tag via the Element#html(String) method, the content is + * Bugfix: when setting the content of a script or style tag via the Element#html(String) method, the content is now treated as a DataNode, not a TextNode. This means that characters like '<' will no longer be incorrectly escaped. As a related ergonomic improvement, the same behavior applies for Element#text(String) (i.e. the content will be treated as a DataNode, despite calling the text() method. diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 8b3eb76b58..c068e1dec6 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1779,4 +1779,21 @@ private static void validateXmlScriptContents(Element el) { assertEquals(p.outerHtml(), p.toString()); assertEquals(i.outerHtml(), i.toString()); } + + @Test public void styleHtmlRoundTrips() { + String styleContents = "foo < bar > qux {color:white;}"; + String html = "<head><style>" + styleContents + "</style> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>"; + Document doc = Jsoup.parse(html); + + Element head = doc.head(); + Element style = head.selectFirst("style"); + assertNotNull(style); + assertEquals(styleContents, style.html()); + style.html(styleContents); + assertEquals(styleContents, style.html()); + assertEquals("", style.text()); + style.text(styleContents); // pushes the HTML, not the Text + assertEquals("", style.text()); + assertEquals(styleContents, style.html()); + } } From 45f62328cf44c396ad15be266197807cf42ca0f8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 29 Dec 2020 14:50:46 +1100 Subject: [PATCH 489/774] Typo --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f89f7013fd..2cd90933b1 100644 --- a/CHANGES +++ b/CHANGES @@ -62,7 +62,7 @@ jsoup changelog treated as a DataNode, despite calling the text() method. <https://github.com/jhy/jsoup/issues/1419> - * Bugfix: when wrapping HTML around an existing element with Element#wrap(String), will no take the content as + * Bugfix: when wrapping HTML around an existing element with Element#wrap(String), will now take the content as provided and ignore normal HTML tree-building rules. This allows for e.g. a div tag to be placed inside of p tags. * Bugfix: the Elements#forms() method should return the selected immediate elements that are Forms, not children. From d66decd3c6ce450c19f276e9693a2727b7aa9748 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 29 Dec 2020 16:41:45 +1100 Subject: [PATCH 490/774] Support node.replaceWith(node) during node traversal Closes #1289 --- CHANGES | 3 + .../java/org/jsoup/select/NodeTraversor.java | 11 +++- .../java/org/jsoup/select/NodeVisitor.java | 30 ++++++---- .../java/org/jsoup/select/TraversorTest.java | 57 +++++++++++++++++++ 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 2cd90933b1..f52f5b78f4 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,9 @@ jsoup changelog * Improvement: much better performance in Node#clone() for large and deeply nested documents. Complexity was O(n^2) or worse, now O(n). + * Improvement: during traversal using the NodeTraversor, nodes may now be replaced with Node#replaceWith(Node). + <https://github.com/jhy/jsoup/issues/1289> + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index 99488277d6..74b66b4e26 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -19,18 +19,23 @@ public class NodeTraversor { */ public static void traverse(NodeVisitor visitor, Node root) { Node node = root; + Node parent; // remember parent to find nodes that get replaced in .head int depth = 0; while (node != null) { - visitor.head(node, depth); - if (node.childNodeSize() > 0) { + parent = node.parentNode(); + visitor.head(node, depth); // visit current node + if (parent != null && !node.hasParent()) // must have been replaced; find replacement + node = parent.childNode(node.siblingIndex()); // replace ditches parent but keeps sibling index + + if (node.childNodeSize() > 0) { // descend node = node.childNode(0); depth++; } else { while (true) { assert node != null; // as depth > 0, will have parent if (!(node.nextSibling() == null && depth > 0)) break; - visitor.tail(node, depth); + visitor.tail(node, depth); // when no more siblings, ascend node = node.parentNode(); depth--; } diff --git a/src/main/java/org/jsoup/select/NodeVisitor.java b/src/main/java/org/jsoup/select/NodeVisitor.java index 08092979c3..5f0011ad53 100644 --- a/src/main/java/org/jsoup/select/NodeVisitor.java +++ b/src/main/java/org/jsoup/select/NodeVisitor.java @@ -1,31 +1,37 @@ package org.jsoup.select; +import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; /** * Node visitor interface. Provide an implementing class to {@link NodeTraversor} to iterate through nodes. * <p> * This interface provides two methods, {@code head} and {@code tail}. The head method is called when the node is first - * seen, and the tail method when all of the node's children have been visited. As an example, head can be used to - * create a start tag for a node, and tail to create the end tag. + * seen, and the tail method when all of the node's children have been visited. As an example, {@code head} can be used to + * emit a start tag for a node, and {@code tail} to create the end tag. * </p> */ public interface NodeVisitor { /** - * Callback for when a node is first visited. - * - * @param node the node being visited. - * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node - * of that will have depth 1. + Callback for when a node is first visited. + <p>The node may be modified (e.g. {@link Node#attr(String)} or replaced {@link Node#replaceWith(Node)}). If it's + {@code instanceOf Element}, you may cast it to an {@link Element} and access those methods.</p> + <p>Note that nodes may not be removed during traversal using this method; use {@link + NodeTraversor#filter(NodeFilter, Node)} with a {@link NodeFilter.FilterResult#REMOVE} return instead.</p> + + @param node the node being visited. + @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node + of that will have depth 1. */ void head(Node node, int depth); /** - * Callback for when a node is last visited, after all of its descendants have been visited. - * - * @param node the node being visited. - * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node - * of that will have depth 1. + Callback for when a node is last visited, after all of its descendants have been visited. + <p>Note that replacement with {@link Node#replaceWith(Node)}</p> is not supported in {@code tail}. + + @param node the node being visited. + @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node + of that will have depth 1. */ void tail(Node node, int depth); } diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java index 00f70d32ac..c5d8a2f201 100644 --- a/src/test/java/org/jsoup/select/TraversorTest.java +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -2,10 +2,12 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class TraversorTest { // Note: NodeTraversor.traverse(new NodeVisitor) is tested in @@ -114,4 +116,59 @@ public FilterResult tail(Node node, int depth) { }, doc.select("div")); assertEquals("<div><p><#text></#text></p>", accum.toString()); } + + @Test public void replaceElement() { + // https://github.com/jhy/jsoup/issues/1289 + // test we can replace an element during traversal + String html = "<div><p>One <i>two</i> <i>three</i> four.</p></div>"; + Document doc = Jsoup.parse(html); + + NodeTraversor.traverse(new NodeVisitor() { + @Override + public void head(Node node, int depth) { + if (node instanceof Element) { + Element el = (Element) node; + if (el.normalName().equals("i")) { + Element u = new Element("u").insertChildren(0, el.childNodes()); + el.replaceWith(u); + } + } + } + + @Override + public void tail(Node node, int depth) {} + }, doc); + + Element p = doc.selectFirst("p"); + assertNotNull(p); + assertEquals("<p>One <u>two</u> <u>three</u> four.</p>", p.outerHtml()); + } + + @Test public void canAddChildren() { + Document doc = Jsoup.parse("<div><p></p><p></p></div>"); + + NodeTraversor.traverse(new NodeVisitor() { + int i = 0; + @Override + public void head(Node node, int depth) { + if (node.nodeName().equals("p")) { + Element p = (Element) node; + p.append("<span>" + i++ + "</span>"); + } + } + + @Override + public void tail(Node node, int depth) { + if (node.nodeName().equals("p")) { + Element p = (Element) node; + p.append("<span>" + i++ + "</span>"); + } + } + }, doc); + + assertEquals("<div>\n" + + " <p><span>0</span><span>1</span></p>\n" + + " <p><span>2</span><span>3</span></p>\n" + + "</div>", doc.body().html()); + } } From fbd74e726108934eb7002a24aeef60c589eafdef Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 29 Dec 2020 17:07:29 +1100 Subject: [PATCH 491/774] Doc hints for rename --- src/main/java/org/jsoup/nodes/Element.java | 3 ++- src/main/java/org/jsoup/select/Elements.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 376a281c11..ab87f73cb7 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -156,11 +156,12 @@ public String normalName() { } /** - * Change the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with + * Change (rename) the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with * {@code el.tagName("div");}. * * @param tagName new tag name for this element * @return this element, for chaining + * @see Elements#tagName(String) */ public Element tagName(String tagName) { Validate.notEmpty(tagName, "Tag name must not be empty."); diff --git a/src/main/java/org/jsoup/select/Elements.java b/src/main/java/org/jsoup/select/Elements.java index 88dc51f17f..31838e1f89 100644 --- a/src/main/java/org/jsoup/select/Elements.java +++ b/src/main/java/org/jsoup/select/Elements.java @@ -294,8 +294,9 @@ public String toString() { } /** - * Update the tag name of each matched element. For example, to change each {@code <i>} to a {@code <em>}, do + * Update (rename) the tag name of each matched element. For example, to change each {@code <i>} to a {@code <em>}, do * {@code doc.select("i").tagName("em");} + * * @param tagName the new tag name * @return this, for chaining * @see Element#tagName(String) From 3afde5830e5bc811dfaf214be86652e2c1f95489 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 30 Dec 2020 12:48:07 +1100 Subject: [PATCH 492/774] Added appendChildren and prependChildren, and wrap the Node children to prevent CMEs Added Element#insertChildren and Elment#prependChildren, as convenience methods in addition to Element#insertChildren(index, children), for bulk moving nodes. Although Node#childNodes() returns an UnmodifiableList as a view into its children, it was still directly backed by the internal child list. That made some uses, such as looping and moving those children to another element, throw a ConcurrentModificationException. Now this method returns its own list so that they are separated and changes to the parent's contents will not impact the children view. Fixes #1431 --- CHANGES | 9 + src/main/java/org/jsoup/nodes/Document.java | 2 +- src/main/java/org/jsoup/nodes/Element.java | 30 +- src/main/java/org/jsoup/nodes/Node.java | 7 +- src/test/java/org/jsoup/nodes/ElementIT.java | 6 +- .../java/org/jsoup/nodes/ElementTest.java | 512 ++++++++++++------ 6 files changed, 384 insertions(+), 182 deletions(-) diff --git a/CHANGES b/CHANGES index f52f5b78f4..8bb76683fc 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,15 @@ jsoup changelog * Improvement: during traversal using the NodeTraversor, nodes may now be replaced with Node#replaceWith(Node). <https://github.com/jhy/jsoup/issues/1289> + * Improvement: added Element#insertChildren and Elment#prependChildren, as convenience methods in addition to + Element#insertChildren(index, children), for bulk moving nodes. + + * Improvement: although Node#childNodes() returns an UnmodifiableList as a view into its children, it was still + directly backed by the internal child list. That made some uses, such as looping and moving those children to + another element, throw a ConcurrentModificationException. Now this method returns its own list so that they are + separated and changes to the parent's contents will not impact the children view. + <https://github.com/jhy/jsoup/issues/1431> + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index b6d3660f98..c9d2bf20a1 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -354,7 +354,7 @@ private void ensureMetaCharsetElement() { } select("meta[name=charset]").remove(); // Remove obsolete elements } else if (syntax == OutputSettings.Syntax.xml) { - Node node = childNodes().get(0); + Node node = ensureChildNodes().get(0); if (node instanceof XmlDeclaration) { XmlDeclaration decl = (XmlDeclaration) node; if (decl.name().equals("xml")) { diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index ab87f73cb7..ef14972af7 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -498,10 +498,12 @@ public boolean is(Evaluator evaluator) { } /** - * Add a node child node to this element. + * Insert a node to the end of this Element's children. The incoming node will be re-parented. * * @param child node to add. - * @return this element, so that you can add more child nodes or elements. + * @return this Element, for chaining + * @see #prependChild(Node) + * @see #insertChildren(int, Collection) */ public Element appendChild(Node child) { Validate.notNull(child); @@ -514,6 +516,18 @@ public Element appendChild(Node child) { return this; } + /** + Insert the given nodes to the end of this Element's children. + + @param children nodes to add + @return this Element, for chaining + @see #insertChildren(int, Collection) + */ + public Element appendChildren(Collection<? extends Node> children) { + insertChildren(-1, children); + return this; + } + /** * Add this element to the supplied parent element, as its next child. * @@ -539,6 +553,18 @@ public Element prependChild(Node child) { return this; } + /** + Insert the given nodes to the start of this Element's children. + + @param children nodes to add + @return this Element, for chaining + @see #insertChildren(int, Collection) + */ + public Element prependChildren(Collection<? extends Node> children) { + insertChildren(0, children); + return this; + } + /** * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 3030329635..b28c44ddad 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -209,7 +209,10 @@ public Node childNode(int index) { @return list of children. If no children, returns an empty list. */ public List<Node> childNodes() { - return Collections.unmodifiableList(ensureChildNodes()); + List<Node> children = ensureChildNodes(); + List<Node> rewrap = new ArrayList<>(children.size()); // wrapped so that looping and moving will not throw a CME as the source changes + rewrap.addAll(children); + return Collections.unmodifiableList(rewrap); } /** @@ -487,7 +490,7 @@ protected void addChildren(int index, Node... children) { final Node firstParent = children[0].parent(); if (firstParent != null && firstParent.childNodeSize() == children.length) { boolean sameList = true; - final List<Node> firstParentNodes = firstParent.childNodes(); + final List<Node> firstParentNodes = firstParent.ensureChildNodes(); // identity check contents to see if same int i = children.length; while (i-- > 0) { diff --git a/src/test/java/org/jsoup/nodes/ElementIT.java b/src/test/java/org/jsoup/nodes/ElementIT.java index 6b0add641e..80557bbf04 100644 --- a/src/test/java/org/jsoup/nodes/ElementIT.java +++ b/src/test/java/org/jsoup/nodes/ElementIT.java @@ -29,7 +29,8 @@ public void testFastReparent() { long runtime = System.currentTimeMillis() - start; assertEquals(rows, wrapper.childNodes.size()); - assertEquals(0, childNodes.size()); // all moved out + assertEquals(rows, childNodes.size()); // child nodes is a wrapper, so still there + assertEquals(0, doc.body().childNodes().size()); // but on a fresh look, all gone doc.body().empty().appendChild(wrapper); Element wrapperAcutal = doc.body().children().get(0); @@ -63,7 +64,8 @@ public void testFastReparentExistingContent() { long runtime = System.currentTimeMillis() - start; assertEquals(rows + 2, wrapper.childNodes.size()); - assertEquals(0, childNodes.size()); // all moved out + assertEquals(rows, childNodes.size()); // child nodes is a wrapper, so still there + assertEquals(0, doc.body().childNodes().size()); // but on a fresh look, all gone doc.body().empty().appendChild(wrapper); Element wrapperAcutal = doc.body().children().get(0); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index c068e1dec6..ccbc1d4375 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -4,36 +4,64 @@ import org.jsoup.TextUtil; import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; -import org.jsoup.select.*; +import org.jsoup.select.Elements; +import org.jsoup.select.Evaluator; +import org.jsoup.select.NodeFilter; +import org.jsoup.select.NodeVisitor; +import org.jsoup.select.QueryParser; import org.junit.jupiter.api.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.*; /** - * Tests for Element (DOM stuff mostly). - * - * @author Jonathan Hedley - */ + Tests for Element (DOM stuff mostly). + + @author Jonathan Hedley */ public class ElementTest { private String reference = "<div id=div1><p>Hello</p><p>Another <b>element</b></p><div id=div2><img src=foo.png></div></div>"; - @Test public void testId() { + private static void validateScriptContents(String src, Element el) { + assertEquals("", el.text()); // it's not text + assertEquals("", el.ownText()); + assertEquals("", el.wholeText()); + assertEquals(src, el.html()); + assertEquals(src, el.data()); + } + + private static void validateXmlScriptContents(Element el) { + assertEquals("var foo = 5 < 2; var bar = 1 && 2;", el.text()); + assertEquals("var foo = 5 < 2; var bar = 1 && 2;", el.ownText()); + assertEquals("var foo = 5 < 2;\nvar bar = 1 && 2;", el.wholeText()); + assertEquals("var foo = 5 &lt; 2;\nvar bar = 1 &amp;&amp; 2;", el.html()); + assertEquals("", el.data()); + } + + @Test + public void testId() { Document doc = Jsoup.parse("<div id=Foo>"); Element el = doc.selectFirst("div"); assertEquals("Foo", el.id()); } - @Test public void testSetId() { + @Test + public void testSetId() { Document doc = Jsoup.parse("<div id=Boo>"); Element el = doc.selectFirst("div"); el.id("Foo"); assertEquals("Foo", el.id()); } - @Test public void getElementsByTagName() { + @Test + public void getElementsByTagName() { Document doc = Jsoup.parse(reference); List<Element> divs = doc.getElementsByTag("div"); assertEquals(2, divs.size()); @@ -54,7 +82,8 @@ public class ElementTest { assertEquals(0, empty.size()); } - @Test public void getNamespacedElementsByTag() { + @Test + public void getNamespacedElementsByTag() { Document doc = Jsoup.parse("<div><abc:def id=1>Hello</abc:def></div>"); Elements els = doc.getElementsByTag("abc:def"); assertEquals(1, els.size()); @@ -62,7 +91,8 @@ public class ElementTest { assertEquals("abc:def", els.first().tagName()); } - @Test public void testGetElementById() { + @Test + public void testGetElementById() { Document doc = Jsoup.parse(reference); Element div = doc.getElementById("div1"); assertEquals("div1", div.id()); @@ -75,47 +105,54 @@ public class ElementTest { assertEquals("span", span.tagName()); } - @Test public void testGetText() { + @Test + public void testGetText() { Document doc = Jsoup.parse(reference); assertEquals("Hello Another element", doc.text()); assertEquals("Another element", doc.getElementsByTag("p").get(1).text()); } - @Test public void testGetChildText() { + @Test + public void testGetChildText() { Document doc = Jsoup.parse("<p>Hello <b>there</b> now"); Element p = doc.select("p").first(); assertEquals("Hello there now", p.text()); assertEquals("Hello now", p.ownText()); } - @Test public void testNormalisesText() { + @Test + public void testNormalisesText() { String h = "<p>Hello<p>There.</p> \n <p>Here <b>is</b> \n s<b>om</b>e text."; Document doc = Jsoup.parse(h); String text = doc.text(); assertEquals("Hello There. Here is some text.", text); } - @Test public void testKeepsPreText() { + @Test + public void testKeepsPreText() { String h = "<p>Hello \n \n there.</p> <div><pre> What's \n\n that?</pre>"; Document doc = Jsoup.parse(h); assertEquals("Hello there. What's \n\n that?", doc.text()); } - @Test public void testKeepsPreTextInCode() { + @Test + public void testKeepsPreTextInCode() { String h = "<pre><code>code\n\ncode</code></pre>"; Document doc = Jsoup.parse(h); assertEquals("code\n\ncode", doc.text()); assertEquals("<pre><code>code\n\ncode</code></pre>", doc.body().html()); } - @Test public void testKeepsPreTextAtDepth() { + @Test + public void testKeepsPreTextAtDepth() { String h = "<pre><code><span><b>code\n\ncode</b></span></code></pre>"; Document doc = Jsoup.parse(h); assertEquals("code\n\ncode", doc.text()); assertEquals("<pre><code><span><b>code\n\ncode</b></span></code></pre>", doc.body().html()); } - @Test public void testBrHasSpace() { + @Test + public void testBrHasSpace() { Document doc = Jsoup.parse("<p>Hello<br>there</p>"); assertEquals("Hello there", doc.text()); assertEquals("Hello there", doc.select("p").first().ownText()); @@ -124,7 +161,8 @@ public class ElementTest { assertEquals("Hello there", doc.text()); } - @Test public void testWholeText() { + @Test + public void testWholeText() { Document doc = Jsoup.parse("<p> Hello\nthere &nbsp; </p>"); assertEquals(" Hello\nthere   ", doc.wholeText()); @@ -135,7 +173,8 @@ public class ElementTest { assertEquals("Hello \n there", doc.wholeText()); } - @Test public void testGetSiblings() { + @Test + public void testGetSiblings() { Document doc = Jsoup.parse("<div><p>Hello<p id=1>there<p>this<p>is<p>an<p id=last>element</div>"); Element p = doc.getElementById("1"); assertEquals("there", p.text()); @@ -145,7 +184,8 @@ public class ElementTest { assertEquals("element", p.lastElementSibling().text()); } - @Test public void testGetSiblingsWithDuplicateContent() { + @Test + public void testGetSiblingsWithDuplicateContent() { Document doc = Jsoup.parse("<div><p>Hello<p id=1>there<p>this<p>this<p>is<p>an<p id=last>element</div>"); Element p = doc.getElementById("1"); assertEquals("there", p.text()); @@ -157,13 +197,15 @@ public class ElementTest { assertEquals("element", p.lastElementSibling().text()); } - @Test public void testFirstElementSiblingOnOrphan() { + @Test + public void testFirstElementSiblingOnOrphan() { Element p = new Element("p"); assertSame(p, p.firstElementSibling()); assertSame(p, p.lastElementSibling()); } - @Test public void testFirstAndLastSiblings() { + @Test + public void testFirstAndLastSiblings() { Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three"); Element div = doc.selectFirst("div"); Element one = div.child(0); @@ -176,7 +218,8 @@ public class ElementTest { assertSame(three, two.lastElementSibling()); } - @Test public void testGetParents() { + @Test + public void testGetParents() { Document doc = Jsoup.parse("<div><p>Hello <span>there</span></div>"); Element span = doc.select("span").first(); Elements parents = span.parents(); @@ -188,7 +231,8 @@ public class ElementTest { assertEquals("html", parents.get(3).tagName()); } - @Test public void testElementSiblingIndex() { + @Test + public void testElementSiblingIndex() { Document doc = Jsoup.parse("<div><p>One</p>...<p>Two</p>...<p>Three</p>"); Elements ps = doc.select("p"); assertEquals(0, ps.get(0).elementSiblingIndex()); @@ -196,7 +240,8 @@ public class ElementTest { assertEquals(2, ps.get(2).elementSiblingIndex()); } - @Test public void testElementSiblingIndexSameContent() { + @Test + public void testElementSiblingIndexSameContent() { Document doc = Jsoup.parse("<div><p>One</p>...<p>One</p>...<p>One</p>"); Elements ps = doc.select("p"); assertEquals(0, ps.get(0).elementSiblingIndex()); @@ -204,7 +249,8 @@ public class ElementTest { assertEquals(2, ps.get(2).elementSiblingIndex()); } - @Test public void testGetElementsWithClass() { + @Test + public void testGetElementsWithClass() { Document doc = Jsoup.parse("<div class='mellow yellow'><span class=mellow>Hello <b class='yellow'>Yellow!</b></span><p>Empty</p></div>"); List<Element> els = doc.getElementsByClass("mellow"); @@ -221,7 +267,8 @@ public class ElementTest { assertEquals(0, none.size()); } - @Test public void testGetElementsWithAttribute() { + @Test + public void testGetElementsWithAttribute() { Document doc = Jsoup.parse("<div style='bold'><p title=qux><p><b style></b></p></div>"); List<Element> els = doc.getElementsByAttribute("style"); assertEquals(2, els.size()); @@ -232,14 +279,16 @@ public class ElementTest { assertEquals(0, none.size()); } - @Test public void testGetElementsWithAttributeDash() { + @Test + public void testGetElementsWithAttributeDash() { Document doc = Jsoup.parse("<meta http-equiv=content-type value=utf8 id=1> <meta name=foo content=bar id=2> <div http-equiv=content-type value=utf8 id=3>"); Elements meta = doc.select("meta[http-equiv=content-type], meta[charset]"); assertEquals(1, meta.size()); assertEquals("1", meta.first().id()); } - @Test public void testGetElementsWithAttributeValue() { + @Test + public void testGetElementsWithAttributeValue() { Document doc = Jsoup.parse("<div style='bold'><p><p><b style></b></p></div>"); List<Element> els = doc.getElementsByAttributeValue("style", "bold"); assertEquals(1, els.size()); @@ -249,7 +298,8 @@ public class ElementTest { assertEquals(0, none.size()); } - @Test public void testClassDomMethods() { + @Test + public void testClassDomMethods() { Document doc = Jsoup.parse("<div><span class=' mellow yellow '>Hello <b>Yellow</b></span></div>"); List<Element> els = doc.getElementsByAttribute("class"); Element span = els.get(0); @@ -267,7 +317,8 @@ public class ElementTest { assertFalse(doc.hasClass("mellow")); } - @Test public void testHasClassDomMethods() { + @Test + public void testHasClassDomMethods() { Tag tag = Tag.valueOf("a"); Attributes attribs = new Attributes(); Element el = new Element(tag, "", attribs); @@ -321,8 +372,8 @@ public class ElementTest { assertTrue(hasClass); } - - @Test public void testClassUpdates() { + @Test + public void testClassUpdates() { Document doc = Jsoup.parse("<div class='mellow yellow'></div>"); Element div = doc.select("div").first(); @@ -335,35 +386,41 @@ public class ElementTest { assertEquals("mellow red", div.className()); } - @Test public void testOuterHtml() { + @Test + public void testOuterHtml() { Document doc = Jsoup.parse("<div title='Tags &amp;c.'><img src=foo.png><p><!-- comment -->Hello<p>there"); assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div title=\"Tags &amp;c.\"><img src=\"foo.png\"><p><!-- comment -->Hello</p><p>there</p></div></body></html>", - TextUtil.stripNewlines(doc.outerHtml())); + TextUtil.stripNewlines(doc.outerHtml())); } - @Test public void testInnerHtml() { + @Test + public void testInnerHtml() { Document doc = Jsoup.parse("<div>\n <p>Hello</p> </div>"); assertEquals("<p>Hello</p>", doc.getElementsByTag("div").get(0).html()); } - @Test public void testFormatHtml() { + @Test + public void testFormatHtml() { Document doc = Jsoup.parse("<title>Format test</title><div><p>Hello <span>jsoup <span>users</span></span></p><p>Good.</p></div>"); assertEquals("<html>\n <head>\n <title>Format test</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body>\n <div>\n <p>Hello <span>jsoup <span>users</span></span></p>\n <p>Good.</p>\n </div>\n </body>\n</html>", doc.html()); } - @Test public void testFormatOutline() { + @Test + public void testFormatOutline() { Document doc = Jsoup.parse("<title>Format test</title><div><p>Hello <span>jsoup <span>users</span></span></p><p>Good.</p></div>"); doc.outputSettings().outline(true); assertEquals("<html>\n <head>\n <title>Format test</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body>\n <div>\n <p>\n Hello \n <span>\n jsoup \n <span>users</span>\n </span>\n </p>\n <p>Good.</p>\n </div>\n </body>\n</html>", doc.html()); } - @Test public void testSetIndent() { + @Test + public void testSetIndent() { Document doc = Jsoup.parse("<div><p>Hello\nthere</p></div>"); doc.outputSettings().indentAmount(0); assertEquals("<html>\n<head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<div>\n<p>Hello there</p>\n</div>\n</body>\n</html>", doc.html()); } - @Test public void testNotPretty() { + @Test + public void testNotPretty() { Document doc = Jsoup.parse("<div> \n<p>Hello\n there\n</p></div>"); doc.outputSettings().prettyPrint(false); assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div> \n<p>Hello\n there\n</p></div></body></html>", doc.html()); @@ -372,7 +429,8 @@ public class ElementTest { assertEquals(" \n<p>Hello\n there\n</p>", div.html()); } - @Test public void testNotPrettyWithEnDashBody() { + @Test + public void testNotPrettyWithEnDashBody() { String html = "<div><span>1:15</span>&ndash;<span>2:15</span>&nbsp;p.m.</div>"; Document document = Jsoup.parse(html); document.outputSettings().prettyPrint(false); @@ -380,14 +438,16 @@ public class ElementTest { assertEquals("<div><span>1:15</span>–<span>2:15</span>&nbsp;p.m.</div>", document.body().html()); } - @Test public void testPrettyWithEnDashBody() { + @Test + public void testPrettyWithEnDashBody() { String html = "<div><span>1:15</span>&ndash;<span>2:15</span>&nbsp;p.m.</div>"; Document document = Jsoup.parse(html); assertEquals("<div>\n <span>1:15</span>–<span>2:15</span>&nbsp;p.m.\n</div>", document.body().html()); } - @Test public void testPrettyAndOutlineWithEnDashBody() { + @Test + public void testPrettyAndOutlineWithEnDashBody() { String html = "<div><span>1:15</span>&ndash;<span>2:15</span>&nbsp;p.m.</div>"; Document document = Jsoup.parse(html); document.outputSettings().outline(true); @@ -395,40 +455,45 @@ public class ElementTest { assertEquals("<div>\n <span>1:15</span>\n –\n <span>2:15</span>\n &nbsp;p.m.\n</div>", document.body().html()); } - @Test public void testBasicFormats() { + @Test + public void testBasicFormats() { String html = "<span>0</span>.<div><span>1</span>-<span>2</span><p><span>3</span>-<span>4</span><div>5</div>"; Document doc = Jsoup.parse(html); assertEquals( "<span>0</span>.\n" + - "<div>\n" + - " <span>1</span>-<span>2</span>\n" + - " <p><span>3</span>-<span>4</span></p>\n" + - " <div>\n" + - " 5\n" + - " </div>\n" + - "</div>", doc.body().html()); + "<div>\n" + + " <span>1</span>-<span>2</span>\n" + + " <p><span>3</span>-<span>4</span></p>\n" + + " <div>\n" + + " 5\n" + + " </div>\n" + + "</div>", doc.body().html()); } - @Test public void testEmptyElementFormatHtml() { + @Test + public void testEmptyElementFormatHtml() { // don't put newlines into empty blocks Document doc = Jsoup.parse("<section><div></div></section>"); assertEquals("<section>\n <div></div>\n</section>", doc.select("section").first().outerHtml()); } - @Test public void testNoIndentOnScriptAndStyle() { + @Test + public void testNoIndentOnScriptAndStyle() { // don't newline+indent closing </script> and </style> tags Document doc = Jsoup.parse("<script>one\ntwo</script>\n<style>three\nfour</style>"); assertEquals("<script>one\ntwo</script> \n<style>three\nfour</style>", doc.head().html()); } - @Test public void testContainerOutput() { + @Test + public void testContainerOutput() { Document doc = Jsoup.parse("<title>Hello there</title> <div><p>Hello</p><p>there</p></div> <div>Another</div>"); assertEquals("<title>Hello there</title>", doc.select("title").first().outerHtml()); assertEquals("<div>\n <p>Hello</p>\n <p>there</p>\n</div>", doc.select("div").first().outerHtml()); assertEquals("<div>\n <p>Hello</p>\n <p>there</p>\n</div> \n<div>\n Another\n</div>", doc.select("body").first().html()); } - @Test public void testSetText() { + @Test + public void testSetText() { String h = "<div id=1>Hello <p>there <b>now</b></p></div>"; Document doc = Jsoup.parse(h); assertEquals("Hello there now", doc.text()); // need to sort out node whitespace @@ -439,14 +504,15 @@ public class ElementTest { assertEquals(0, doc.select("p").size()); } - @Test public void testAddNewElement() { + @Test + public void testAddNewElement() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.appendElement("p").text("there"); div.appendElement("P").attr("CLASS", "second").text("now"); // manually specifying tag and attributes should maintain case based on parser settings assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div id=\"1\"><p>Hello</p><p>there</p><p class=\"second\">now</p></div></body></html>", - TextUtil.stripNewlines(doc.html())); + TextUtil.stripNewlines(doc.html())); // check sibling index (with short circuit on reindexChildren): Elements ps = doc.select("p"); @@ -455,7 +521,8 @@ public class ElementTest { } } - @Test public void testAddBooleanAttribute() { + @Test + public void testAddBooleanAttribute() { Element div = new Element(Tag.valueOf("div"), ""); div.attr("true", true); @@ -473,7 +540,8 @@ public class ElementTest { assertEquals("<div true></div>", div.outerHtml()); } - @Test public void testAppendRowToTable() { + @Test + public void testAppendRowToTable() { Document doc = Jsoup.parse("<table><tr><td>1</td></tr></table>"); Element table = doc.select("tbody").first(); table.append("<tr><td>2</td></tr>"); @@ -481,7 +549,8 @@ public class ElementTest { assertEquals("<table><tbody><tr><td>1</td></tr><tr><td>2</td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); } - @Test public void testPrependRowToTable() { + @Test + public void testPrependRowToTable() { Document doc = Jsoup.parse("<table><tr><td>1</td></tr></table>"); Element table = doc.select("tbody").first(); table.prepend("<tr><td>2</td></tr>"); @@ -495,7 +564,8 @@ public class ElementTest { } } - @Test public void testPrependElement() { + @Test + public void testPrependElement() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.prependElement("p").text("Before"); @@ -503,14 +573,16 @@ public class ElementTest { assertEquals("Hello", div.child(1).text()); } - @Test public void testAddNewText() { + @Test + public void testAddNewText() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.appendText(" there & now >"); assertEquals("<p>Hello</p> there &amp; now &gt;", TextUtil.stripNewlines(div.html())); } - @Test public void testPrependText() { + @Test + public void testPrependText() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.prependText("there & now > "); @@ -518,7 +590,8 @@ public class ElementTest { assertEquals("there &amp; now &gt; <p>Hello</p>", TextUtil.stripNewlines(div.html())); } - @Test public void testThrowsOnAddNullText() { + @Test + public void testThrowsOnAddNullText() { assertThrows(IllegalArgumentException.class, () -> { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -526,7 +599,8 @@ public class ElementTest { }); } - @Test public void testThrowsOnPrependNullText() { + @Test + public void testThrowsOnPrependNullText() { assertThrows(IllegalArgumentException.class, () -> { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); @@ -534,7 +608,8 @@ public class ElementTest { }); } - @Test public void testAddNewHtml() { + @Test + public void testAddNewHtml() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.append("<p>there</p><p>now</p>"); @@ -547,7 +622,8 @@ public class ElementTest { } } - @Test public void testPrependNewHtml() { + @Test + public void testPrependNewHtml() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.prepend("<p>there</p><p>now</p>"); @@ -560,14 +636,16 @@ public class ElementTest { } } - @Test public void testSetHtml() { + @Test + public void testSetHtml() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.html("<p>there</p><p>now</p>"); assertEquals("<p>there</p><p>now</p>", TextUtil.stripNewlines(div.html())); } - @Test public void testSetHtmlTitle() { + @Test + public void testSetHtmlTitle() { Document doc = Jsoup.parse("<html><head id=2><title id=1></title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head></html>"); Element title = doc.getElementById("1"); @@ -581,7 +659,8 @@ public class ElementTest { assertEquals("<title>&lt;i&gt;bad&lt;/i&gt;</title>", head.html()); } - @Test public void testWrap() { + @Test + public void testWrap() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p = doc.select("p").first(); p.wrap("<div class='head'></div>"); @@ -589,12 +668,13 @@ public class ElementTest { Element ret = p.wrap("<div><div class=foo></div><p>What?</p></div>"); assertEquals("<div><div class=\"head\"><div><div class=\"foo\"><p>Hello</p></div><p>What?</p></div></div><p>There</p></div>", - TextUtil.stripNewlines(doc.body().html())); + TextUtil.stripNewlines(doc.body().html())); assertEquals(ret, p); } - @Test public void testWrapNoop() { + @Test + public void testWrapNoop() { Document doc = Jsoup.parse("<div><p>Hello</p></div>"); Node p = doc.select("p").first(); Node wrapped = p.wrap("Some junk"); @@ -603,7 +683,8 @@ public class ElementTest { // should be a NOOP } - @Test public void testWrapOnOrphan() { + @Test + public void testWrapOnOrphan() { Element orphan = new Element("span").text("Hello!"); assertFalse(orphan.hasParent()); Element wrapped = orphan.wrap("<div></div> There!"); @@ -614,7 +695,8 @@ public class ElementTest { assertEquals("<div>\n <span>Hello!</span>\n</div>", orphan.parent().outerHtml()); } - @Test public void testWrapArtificialStructure() { + @Test + public void testWrapArtificialStructure() { // div normally couldn't get into a p, but explicitly want to wrap Document doc = Jsoup.parse("<p>Hello <i>there</i> now."); Element i = doc.selectFirst("i"); @@ -623,7 +705,8 @@ public class ElementTest { assertEquals("<p>Hello <div id=\"id1\"><i>there</i></div> quite now.</p>", TextUtil.stripNewlines(doc.body().html())); } - @Test public void before() { + @Test + public void before() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p1 = doc.select("p").first(); p1.before("<div>one</div><div>two</div>"); @@ -633,7 +716,8 @@ public class ElementTest { assertEquals("<div><div>one</div><div>two</div><p>Hello</p><p>Three</p><!-- four --><p>There</p></div>", TextUtil.stripNewlines(doc.body().html())); } - @Test public void after() { + @Test + public void after() { Document doc = Jsoup.parse("<div><p>Hello</p><p>There</p></div>"); Element p1 = doc.select("p").first(); p1.after("<div>one</div><div>two</div>"); @@ -643,14 +727,16 @@ public class ElementTest { assertEquals("<div><p>Hello</p><div>one</div><div>two</div><p>There</p><p>Three</p><!-- four --></div>", TextUtil.stripNewlines(doc.body().html())); } - @Test public void testWrapWithRemainder() { + @Test + public void testWrapWithRemainder() { Document doc = Jsoup.parse("<div><p>Hello</p></div>"); Element p = doc.select("p").first(); p.wrap("<div class='head'></div><p>There!</p>"); assertEquals("<div><div class=\"head\"><p>Hello</p></div><p>There!</p></div>", TextUtil.stripNewlines(doc.body().html())); } - @Test public void testWrapWithSimpleRemainder() { + @Test + public void testWrapWithSimpleRemainder() { Document doc = Jsoup.parse("<p>Hello"); Element p = doc.selectFirst("p"); Element body = p.parent(); @@ -667,7 +753,8 @@ public class ElementTest { assertEquals("<div><p>Hello</p></div> There", TextUtil.stripNewlines(doc.body().html())); } - @Test public void testHasText() { + @Test + public void testHasText() { Document doc = Jsoup.parse("<div><p>Hello</p><p></p></div>"); Element div = doc.select("div").first(); Elements ps = doc.select("p"); @@ -677,7 +764,8 @@ public class ElementTest { assertFalse(ps.last().hasText()); } - @Test public void dataset() { + @Test + public void dataset() { Document doc = Jsoup.parse("<div id=1 data-name=jsoup class=new data-package=jar>Hello</div><p id=2>Hello</p>"); Element div = doc.select("div").first(); Map<String, String> dataset = div.dataset(); @@ -711,7 +799,8 @@ public class ElementTest { } - @Test public void parentlessToString() { + @Test + public void parentlessToString() { Document doc = Jsoup.parse("<img src='foo'>"); Element img = doc.select("img").first(); assertEquals("<img src=\"foo\">", img.toString()); @@ -720,12 +809,14 @@ public class ElementTest { assertEquals("<img src=\"foo\">", img.toString()); } - @Test public void orphanDivToString() { + @Test + public void orphanDivToString() { Element orphan = new Element("div").id("foo").text("Hello"); assertEquals("<div id=\"foo\">\n Hello\n</div>", orphan.toString()); } - @Test public void testClone() { + @Test + public void testClone() { Document doc = Jsoup.parse("<div><p>One<p><span>Two</div>"); Element p = doc.select("p").get(1); @@ -745,7 +836,8 @@ public class ElementTest { assertEquals("<div><p>One</p><p><span>Two</span></p></div><p><span>Two</span><span>Three</span></p>", TextUtil.stripNewlines(doc.body().html())); } - @Test public void testClonesClassnames() { + @Test + public void testClonesClassnames() { Document doc = Jsoup.parse("<div class='one two'></div>"); Element div = doc.select("div").first(); Set<String> classes = div.classNames(); @@ -770,7 +862,8 @@ public class ElementTest { assertEquals("", copy.html()); } - @Test public void testShallowClone() { + @Test + public void testShallowClone() { String base = "http://example.com/"; Document doc = Jsoup.parse("<div id=1 class=one><p id=2 class=two>One", base); Element d = doc.selectFirst("div"); @@ -801,7 +894,8 @@ public class ElementTest { assertEquals(base, d2.baseUri()); } - @Test public void testTagNameSet() { + @Test + public void testTagNameSet() { Document doc = Jsoup.parse("<div><i>Hello</i>"); doc.select("i").first().tagName("em"); assertEquals(0, doc.select("i").size()); @@ -809,14 +903,16 @@ public class ElementTest { assertEquals("<em>Hello</em>", doc.select("div").first().html()); } - @Test public void testHtmlContainsOuter() { + @Test + public void testHtmlContainsOuter() { Document doc = Jsoup.parse("<title>Check</title> <div>Hello there</div>"); doc.outputSettings().indentAmount(0); assertTrue(doc.html().contains(doc.select("title").outerHtml())); assertTrue(doc.html().contains(doc.select("div").outerHtml())); } - @Test public void testGetTextNodes() { + @Test + public void testGetTextNodes() { Document doc = Jsoup.parse("<p>One <span>Two</span> Three <br> Four</p>"); List<TextNode> textNodes = doc.select("p").first().textNodes(); @@ -828,7 +924,8 @@ public class ElementTest { assertEquals(0, doc.select("br").first().textNodes().size()); } - @Test public void testManipulateTextNodes() { + @Test + public void testManipulateTextNodes() { Document doc = Jsoup.parse("<p>One <span>Two</span> Three <br> Four</p>"); Element p = doc.select("p").first(); List<TextNode> textNodes = p.textNodes(); @@ -841,7 +938,8 @@ public class ElementTest { assertEquals(4, p.textNodes().size()); // grew because of split } - @Test public void testGetDataNodes() { + @Test + public void testGetDataNodes() { Document doc = Jsoup.parse("<script>One Two</script> <style>Three Four</style> <p>Fix Six</p>"); Element script = doc.select("script").first(); Element style = doc.select("style").first(); @@ -859,7 +957,8 @@ public class ElementTest { assertEquals(0, pData.size()); } - @Test public void elementIsNotASiblingOfItself() { + @Test + public void elementIsNotASiblingOfItself() { Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three</div>"); Element p2 = doc.select("p").get(1); @@ -870,7 +969,8 @@ public class ElementTest { assertEquals("<p>Three</p>", els.get(1).outerHtml()); } - @Test public void testChildThrowsIndexOutOfBoundsOnMissing() { + @Test + public void testChildThrowsIndexOutOfBoundsOnMissing() { Document doc = Jsoup.parse("<div><p>One</p><p>Two</p></div>"); Element div = doc.select("div").first(); @@ -880,7 +980,8 @@ public class ElementTest { try { div.child(3); fail("Should throw index out of bounds"); - } catch (IndexOutOfBoundsException e) {} + } catch (IndexOutOfBoundsException e) { + } } @Test @@ -897,7 +998,7 @@ public void moveByAppend() { div2.insertChildren(0, children); - assertEquals(0, children.size()); // children is backed by div1.childNodes, moved, so should be 0 now + assertEquals(4, children.size()); // children is NOT backed by div1.childNodes but a wrapper, so should still be 4 (but re-parented) assertEquals(0, div1.childNodeSize()); assertEquals(4, div2.childNodeSize()); assertEquals("<div id=\"1\"></div>\n<div id=\"2\">\n Text \n <p>One</p> Text \n <p>Two</p>\n</div>", @@ -914,7 +1015,8 @@ public void insertChildrenArgumentValidation() { try { div2.insertChildren(6, children); fail(); - } catch (IllegalArgumentException e) {} + } catch (IllegalArgumentException e) { + } try { div2.insertChildren(-5, children); @@ -989,7 +1091,6 @@ public void testCssPath() { assertSame(divC, doc.select(divC.cssSelector()).first()); } - @Test public void testClassNames() { Document doc = Jsoup.parse("<div class=\"c1 c2\">C</div>"); @@ -1004,7 +1105,7 @@ public void testClassNames() { assertEquals("c2", arr1[1]); // Changes to the set should not be reflected in the Elements getters - set1.add("c3"); + set1.add("c3"); assertEquals(2, div.classNames().size()); assertEquals("c1 c2", div.className()); @@ -1015,7 +1116,6 @@ public void testClassNames() { div.classNames(newSet); - assertEquals("c1 c2 c3", div.className()); final Set<String> set2 = div.classNames(); @@ -1031,7 +1131,7 @@ public void testHashAndEqualsAndValue() { // .equals and hashcode are identity. value is content. String doc1 = "<div id=1><p class=one>One</p><p class=one>One</p><p class=one>Two</p><p class=two>One</p></div>" + - "<div id=2><p class=one>One</p><p class=one>One</p><p class=one>Two</p><p class=two>One</p></div>"; + "<div id=2><p class=one>One</p><p class=one>One</p><p class=one>Two</p><p class=two>One</p></div>"; Document doc = Jsoup.parse(doc1); Elements els = doc.select("p"); @@ -1077,7 +1177,8 @@ public void testHashAndEqualsAndValue() { assertNotEquals(e0.hashCode(), (e7).hashCode()); } - @Test public void testRelativeUrls() { + @Test + public void testRelativeUrls() { String html = "<body><a href='./one.html'>One</a> <a href='two.html'>two</a> <a href='../three.html'>Three</a> <a href='//example2.com/four/'>Four</a> <a href='https://example2.com/five/'>Five</a>"; Document doc = Jsoup.parse(html, "http://example.com/bar/"); Elements els = doc.select("a"); @@ -1219,13 +1320,14 @@ public void testClosest() { assertNull(el.closest("p")); } - - @Test public void elementByTagName() { + @Test + public void elementByTagName() { Element a = new Element("P"); assertEquals("P", a.tagName()); } - @Test public void testChildrenElements() { + @Test + public void testChildrenElements() { String html = "<div><p><a>One</a></p><p><a>Two</a></p>Three</div><span>Four</span><foo></foo><img>"; Document doc = Jsoup.parse(html); Element div = doc.select("div").first(); @@ -1254,7 +1356,8 @@ public void testClosest() { assertEquals(0, img.childNodes().size()); } - @Test public void testShadowElementsAreUpdated() { + @Test + public void testShadowElementsAreUpdated() { String html = "<div><p><a>One</a></p><p><a>Two</a></p>Three</div><span>Four</span><foo></foo><img>"; Document doc = Jsoup.parse(html); Element div = doc.select("div").first(); @@ -1294,7 +1397,8 @@ public void testClosest() { "<p>P4</p>Three", div.html()); } - @Test public void classNamesAndAttributeNameIsCaseInsensitive() { + @Test + public void classNamesAndAttributeNameIsCaseInsensitive() { String html = "<p Class='SomeText AnotherText'>One</p>"; Document doc = Jsoup.parse(html); Element p = doc.select("p").first(); @@ -1329,13 +1433,13 @@ public void testClosest() { assertEquals(p1, p10); } - @Test - public void testAppendTo() { - String parentHtml = "<div class='a'></div>"; - String childHtml = "<div class='b'></div><p>Two</p>"; + @Test + public void testAppendTo() { + String parentHtml = "<div class='a'></div>"; + String childHtml = "<div class='b'></div><p>Two</p>"; - Document parentDoc = Jsoup.parse(parentHtml); - Element parent = parentDoc.body(); + Document parentDoc = Jsoup.parse(parentHtml); + Element parent = parentDoc.body(); Document childDoc = Jsoup.parse(childHtml); Element div = childDoc.select("div").first(); @@ -1348,9 +1452,10 @@ public void testAppendTo() { assertEquals("<div class=\"a\"></div>\n<div class=\"b\">\n <p>Two</p>\n</div>", parentDoc.body().html()); assertEquals("", childDoc.body().html()); // got moved out - } + } - @Test public void testNormalizesNbspInText() { + @Test + public void testNormalizesNbspInText() { String escaped = "You can't always get what you&nbsp;want."; String withNbsp = "You can't always get what you want."; // there is an nbsp char in there Document doc = Jsoup.parse("<p>" + escaped); @@ -1366,7 +1471,8 @@ public void testAppendTo() { assertTrue(matched.is(":containsOwn(get what you want)")); } - @Test public void testNormalizesInvisiblesInText() { + @Test + public void testNormalizesInvisiblesInText() { String escaped = "This&shy;is&#x200b;one&shy;long&shy;word"; String decoded = "This\u00ADis\u200Bone\u00ADlong\u00ADword"; // browser would not display those soft hyphens / other chars, so we don't want them in the text @@ -1383,52 +1489,52 @@ public void testAppendTo() { } - @Test - public void testRemoveBeforeIndex() { - Document doc = Jsoup.parse( - "<html><body><div><p>before1</p><p>before2</p><p>XXX</p><p>after1</p><p>after2</p></div></body></html>", - ""); - Element body = doc.select("body").first(); - Elements elems = body.select("p:matchesOwn(XXX)"); - Element xElem = elems.first(); - Elements beforeX = xElem.parent().getElementsByIndexLessThan(xElem.elementSiblingIndex()); - - for(Element p : beforeX) { - p.remove(); - } - - assertEquals("<body><div><p>XXX</p><p>after1</p><p>after2</p></div></body>", TextUtil.stripNewlines(body.outerHtml())); - } + @Test + public void testRemoveBeforeIndex() { + Document doc = Jsoup.parse( + "<html><body><div><p>before1</p><p>before2</p><p>XXX</p><p>after1</p><p>after2</p></div></body></html>", + ""); + Element body = doc.select("body").first(); + Elements elems = body.select("p:matchesOwn(XXX)"); + Element xElem = elems.first(); + Elements beforeX = xElem.parent().getElementsByIndexLessThan(xElem.elementSiblingIndex()); + + for (Element p : beforeX) { + p.remove(); + } - @Test - public void testRemoveAfterIndex() { - Document doc2 = Jsoup.parse( - "<html><body><div><p>before1</p><p>before2</p><p>XXX</p><p>after1</p><p>after2</p></div></body></html>", - ""); - Element body = doc2.select("body").first(); - Elements elems = body.select("p:matchesOwn(XXX)"); - Element xElem = elems.first(); - Elements afterX = xElem.parent().getElementsByIndexGreaterThan(xElem.elementSiblingIndex()); + assertEquals("<body><div><p>XXX</p><p>after1</p><p>after2</p></div></body>", TextUtil.stripNewlines(body.outerHtml())); + } - for(Element p : afterX) { - p.remove(); - } + @Test + public void testRemoveAfterIndex() { + Document doc2 = Jsoup.parse( + "<html><body><div><p>before1</p><p>before2</p><p>XXX</p><p>after1</p><p>after2</p></div></body></html>", + ""); + Element body = doc2.select("body").first(); + Elements elems = body.select("p:matchesOwn(XXX)"); + Element xElem = elems.first(); + Elements afterX = xElem.parent().getElementsByIndexGreaterThan(xElem.elementSiblingIndex()); + + for (Element p : afterX) { + p.remove(); + } - assertEquals("<body><div><p>before1</p><p>before2</p><p>XXX</p></div></body>", TextUtil.stripNewlines(body.outerHtml())); - } + assertEquals("<body><div><p>before1</p><p>before2</p><p>XXX</p></div></body>", TextUtil.stripNewlines(body.outerHtml())); + } @Test - public void whiteSpaceClassElement(){ - Tag tag = Tag.valueOf("a"); - Attributes attribs = new Attributes(); - Element el = new Element(tag, "", attribs); + public void whiteSpaceClassElement() { + Tag tag = Tag.valueOf("a"); + Attributes attribs = new Attributes(); + Element el = new Element(tag, "", attribs); - attribs.put("class", "abc "); - boolean hasClass = el.hasClass("ab"); - assertFalse(hasClass); - } + attribs.put("class", "abc "); + boolean hasClass = el.hasClass("ab"); + assertFalse(hasClass); + } - @Test + @Test public void testNextElementSiblingAfterClone() { // via https://github.com/jhy/jsoup/issues/951 String html = "<!DOCTYPE html><html lang=\"en\"><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div>Initial element</div></body></html>"; @@ -1651,7 +1757,6 @@ public void doesntDeleteZWJWhenNormalizingText() { assertTrue(found.hasSameValue(d)); } - @Test public void testReparentSeperateNodes() { String html = "<div><p>One<p>Two"; @@ -1707,7 +1812,8 @@ public void testChildSizeWithMixedContent() { assertEquals(5, row.childNodeSize()); } - @Test public void isBlock() { + @Test + public void isBlock() { String html = "<div><p><span>Hello</span>"; Document doc = Jsoup.parse(html); assertTrue(doc.selectFirst("div").isBlock()); @@ -1715,7 +1821,8 @@ public void testChildSizeWithMixedContent() { assertFalse(doc.selectFirst("span").isBlock()); } - @Test public void testScriptTextHtmlSetAsData() { + @Test + public void testScriptTextHtmlSetAsData() { String src = "var foo = 5 < 2;\nvar bar = 1 && 2;"; String html = "<script>" + src + "</script>"; Document doc = Jsoup.parse(html); @@ -1749,23 +1856,8 @@ public void testChildSizeWithMixedContent() { } - private static void validateScriptContents(String src, Element el) { - assertEquals("", el.text()); // it's not text - assertEquals("", el.ownText()); - assertEquals("", el.wholeText()); - assertEquals(src, el.html()); - assertEquals(src, el.data()); - } - - private static void validateXmlScriptContents(Element el) { - assertEquals("var foo = 5 < 2; var bar = 1 && 2;", el.text()); - assertEquals("var foo = 5 < 2; var bar = 1 && 2;", el.ownText()); - assertEquals("var foo = 5 < 2;\nvar bar = 1 && 2;", el.wholeText()); - assertEquals("var foo = 5 &lt; 2;\nvar bar = 1 &amp;&amp; 2;", el.html()); - assertEquals("", el.data()); - } - - @Test public void testShallowCloneToString() { + @Test + public void testShallowCloneToString() { // https://github.com/jhy/jsoup/issues/1410 Document doc = Jsoup.parse("<p><i>Hello</i></p>"); Element p = doc.selectFirst("p"); @@ -1780,7 +1872,8 @@ private static void validateXmlScriptContents(Element el) { assertEquals(i.outerHtml(), i.toString()); } - @Test public void styleHtmlRoundTrips() { + @Test + public void styleHtmlRoundTrips() { String styleContents = "foo < bar > qux {color:white;}"; String html = "<head><style>" + styleContents + "</style> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>"; Document doc = Jsoup.parse(html); @@ -1796,4 +1889,73 @@ private static void validateXmlScriptContents(Element el) { assertEquals("", style.text()); assertEquals(styleContents, style.html()); } + + @Test + public void moveChildren() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three</div><div></div>"); + Elements divs = doc.select("div"); + Element a = divs.get(0); + Element b = divs.get(1); + + b.insertChildren(-1, a.childNodes()); + + assertEquals("<div></div>\n<div>\n <p>One</p>\n <p>Two</p>\n <p>Three</p>\n</div>", + doc.body().html()); + } + + @Test + public void moveChildrenToOuter() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three</div><div></div>"); + Elements divs = doc.select("div"); + Element a = divs.get(0); + Element b = doc.body(); + + b.insertChildren(-1, a.childNodes()); + + assertEquals("<div></div>\n<div></div>\n<p>One</p>\n<p>Two</p>\n<p>Three</p>", + doc.body().html()); + } + + @Test + public void appendChildren() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three</div><div><p>Four</div>"); + Elements divs = doc.select("div"); + Element a = divs.get(0); + Element b = divs.get(1); + + b.appendChildren(a.childNodes()); + + assertEquals("<div></div>\n<div>\n <p>Four</p>\n <p>One</p>\n <p>Two</p>\n <p>Three</p>\n</div>", + doc.body().html()); + } + + @Test + public void prependChildren() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three</div><div><p>Four</div>"); + Elements divs = doc.select("div"); + Element a = divs.get(0); + Element b = divs.get(1); + + b.prependChildren(a.childNodes()); + + assertEquals("<div></div>\n<div>\n <p>One</p>\n <p>Two</p>\n <p>Three</p>\n <p>Four</p>\n</div>", + doc.body().html()); + } + + @Test + public void loopMoveChildren() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three</div><div><p>Four</div>"); + Elements divs = doc.select("div"); + Element a = divs.get(0); + Element b = divs.get(1); + + Element outer = b.parent(); + assertNotNull(outer); + for (Node node : a.childNodes()) { + outer.appendChild(node); + } + + assertEquals("<div></div>\n<div>\n <p>Four</p>\n</div>\n<p>One</p>\n<p>Two</p>\n<p>Three</p>", + doc.body().html()); + } } From 28792633d291818bca6cdcc1ed890afe8ca8b4ea Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 30 Dec 2020 15:15:32 +1100 Subject: [PATCH 493/774] Do not auto-vivify attributes or child nodes for read only ops Saves a good amount of memory / garbage, partic for selector attribute traversals --- src/main/java/org/jsoup/nodes/Element.java | 26 ++++++---- src/main/java/org/jsoup/nodes/LeafNode.java | 2 - src/main/java/org/jsoup/nodes/Node.java | 19 ++++++-- .../java/org/jsoup/nodes/ElementTest.java | 47 +++++++++++++++++++ 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index ef14972af7..6cbbf4e938 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -41,9 +41,9 @@ */ @NonnullByDefault public class Element extends Node { - private static final List<Node> EMPTY_NODES = Collections.emptyList(); - private static final Pattern classSplit = Pattern.compile("\\s+"); - private static final String baseUriKey = Attributes.internalKey("baseUri"); + private static final List<Element> EmptyChildren = Collections.emptyList(); + private static final Pattern ClassSplit = Pattern.compile("\\s+"); + private static final String BaseUriKey = Attributes.internalKey("baseUri"); private Tag tag; private @Nullable WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children List<Node> childNodes; @@ -68,7 +68,7 @@ public Element(String tag) { */ public Element(Tag tag, @Nullable String baseUri, @Nullable Attributes attributes) { Validate.notNull(tag); - childNodes = EMPTY_NODES; + childNodes = EmptyNodes; this.attributes = attributes; this.tag = tag; if (baseUri != null) @@ -86,8 +86,15 @@ public Element(Tag tag, String baseUri) { this(tag, baseUri, null); } + /** + Internal test to check if a nodelist object has been created. + */ + protected boolean hasChildNodes() { + return childNodes != EmptyNodes; + } + protected List<Node> ensureChildNodes() { - if (childNodes == EMPTY_NODES) { + if (childNodes == EmptyNodes) { childNodes = new NodeList(this, 4); } return childNodes; @@ -107,7 +114,7 @@ public Attributes attributes() { @Override public String baseUri() { - return searchUpForAttribute(this, baseUriKey); + return searchUpForAttribute(this, BaseUriKey); } private static String searchUpForAttribute(final Element start, final String key) { @@ -122,7 +129,7 @@ private static String searchUpForAttribute(final Element start, final String key @Override protected void doSetBaseUri(String baseUri) { - attributes().put(baseUriKey, baseUri); + attributes().put(BaseUriKey, baseUri); } @Override @@ -322,6 +329,9 @@ public Elements children() { * @return a list of child elements */ List<Element> childElementsList() { + if (childNodeSize() == 0) + return EmptyChildren; // short circuit creating empty + List<Element> children; if (shadowChildrenRef == null || (children = shadowChildrenRef.get()) == null) { final int size = childNodes.size(); @@ -1387,7 +1397,7 @@ public String className() { * @return set of classnames, empty if no class attribute */ public Set<String> classNames() { - String[] names = classSplit.split(className()); + String[] names = ClassSplit.split(className()); Set<String> classNames = new LinkedHashSet<>(Arrays.asList(names)); classNames.remove(""); // if classNames() was empty, would include an empty class diff --git a/src/main/java/org/jsoup/nodes/LeafNode.java b/src/main/java/org/jsoup/nodes/LeafNode.java index 2dd6542417..63eb460d6c 100644 --- a/src/main/java/org/jsoup/nodes/LeafNode.java +++ b/src/main/java/org/jsoup/nodes/LeafNode.java @@ -6,8 +6,6 @@ import java.util.List; abstract class LeafNode extends Node { - private static final List<Node> EmptyNodes = Collections.emptyList(); - Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set) protected final boolean hasAttributes() { diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index b28c44ddad..9e43020b45 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -21,6 +21,7 @@ @author Jonathan Hedley, jonathan@hedley.net */ public abstract class Node implements Cloneable { + static final List<Node> EmptyNodes = Collections.emptyList(); static final String EmptyString = ""; @Nullable Node parentNode; // Nodes don't always have parents int siblingIndex; @@ -105,6 +106,8 @@ public Node attr(String attributeKey, String attributeValue) { */ public boolean hasAttr(String attributeKey) { Validate.notNull(attributeKey); + if (!hasAttributes()) + return false; if (attributeKey.startsWith("abs:")) { String key = attributeKey.substring("abs:".length()); @@ -121,7 +124,8 @@ public boolean hasAttr(String attributeKey) { */ public Node removeAttr(String attributeKey) { Validate.notNull(attributeKey); - attributes().removeIgnoreCase(attributeKey); + if (hasAttributes()) + attributes().removeIgnoreCase(attributeKey); return this; } @@ -130,10 +134,12 @@ public Node removeAttr(String attributeKey) { * @return this, for chaining */ public Node clearAttributes() { - Iterator<Attribute> it = attributes().iterator(); - while (it.hasNext()) { - it.next(); - it.remove(); + if (hasAttributes()) { + Iterator<Attribute> it = attributes().iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } } return this; } @@ -209,6 +215,9 @@ public Node childNode(int index) { @return list of children. If no children, returns an empty list. */ public List<Node> childNodes() { + if (childNodeSize() == 0) + return EmptyNodes; + List<Node> children = ensureChildNodes(); List<Node> rewrap = new ArrayList<>(children.size()); // wrapped so that looping and moving will not throw a CME as the source changes rewrap.addAll(children); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index ccbc1d4375..6913ff9e8c 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1958,4 +1958,51 @@ public void loopMoveChildren() { assertEquals("<div></div>\n<div>\n <p>Four</p>\n</div>\n<p>One</p>\n<p>Two</p>\n<p>Three</p>", doc.body().html()); } + + @Test + public void accessorsDoNotVivifyAttributes() throws NoSuchFieldException, IllegalAccessException { + // internally, we don't want to create empty Attribute objects unless actually used for something + Document doc = Jsoup.parse("<div><p><a href=foo>One</a>"); + Element div = doc.selectFirst("div"); + Element p = doc.selectFirst("p"); + Element a = doc.selectFirst("a"); + + // should not create attributes + assertEquals("", div.attr("href")); + p.removeAttr("href"); + + Elements hrefs = doc.select("[href]"); + assertEquals(1, hrefs.size()); + + assertFalse(div.hasAttributes()); + assertFalse(p.hasAttributes()); + assertTrue(a.hasAttributes()); + } + + @Test + public void childNodesAccessorDoesNotVivify() { + Document doc = Jsoup.parse("<p></p>"); + Element p = doc.selectFirst("p"); + assertFalse(p.hasChildNodes()); + + assertEquals(0, p.childNodeSize()); + assertEquals(0, p.childrenSize()); + + List<Node> childNodes = p.childNodes(); + assertEquals(0, childNodes.size()); + + Elements children = p.children(); + assertEquals(0, children.size()); + + assertFalse(p.hasChildNodes()); + } + + @Test void emptyChildrenElementsIsModifiable() { + // using unmodifiable empty in childElementList as short circuit, but people may be modifying Elements. + Element p = new Element("p"); + Elements els = p.children(); + assertEquals(0, els.size()); + els.add(new Element("a")); + assertEquals(1, els.size()); + } } From 79496d8d047f2d0774e0ad9d8169a021cb828fab Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 31 Dec 2020 12:41:49 +1100 Subject: [PATCH 494/774] Call out behavior change on childNodes See #1431 --- CHANGES | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 8bb76683fc..cdb34be2c4 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,14 @@ jsoup changelog * Change: updated the minimum Android API level from 8 to 10. + * Change: although Node#childNodes() returns an UnmodifiableList as a view into its children, it was still + directly backed by the internal child list. That made some uses, such as looping and moving those children to + another element, throw a ConcurrentModificationException. Now this method returns its own list so that they are + separated and changes to the parent's contents will not impact the children view. This aligns with similar methods + such as Element#children(). If you have code that iterates this list and makes parenting changes to its contents, + you may need to make a code update. + <https://github.com/jhy/jsoup/issues/1431> + * Improvement: renamed the Whitelist class to Safelist, with the goal of more inclusive language. A shim is provided for backwards compatibility (source and binary). This shim is marked as deprecated and will be removed in the jsoup 1.15.1 release. @@ -40,12 +48,6 @@ jsoup changelog * Improvement: added Element#insertChildren and Elment#prependChildren, as convenience methods in addition to Element#insertChildren(index, children), for bulk moving nodes. - * Improvement: although Node#childNodes() returns an UnmodifiableList as a view into its children, it was still - directly backed by the internal child list. That made some uses, such as looping and moving those children to - another element, throw a ConcurrentModificationException. Now this method returns its own list so that they are - separated and changes to the parent's contents will not impact the children view. - <https://github.com/jhy/jsoup/issues/1431> - * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. From 032f021538a1cca90278c2cdf5321c95b7b61083 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 31 Dec 2020 13:59:28 +1100 Subject: [PATCH 495/774] Javadoc fixes for first sentence --- src/main/java/org/jsoup/nodes/Node.java | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 9e43020b45..216e5fdc28 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -44,8 +44,8 @@ Get the node name of this node. Use for debugging purposes and not logic switchi protected abstract boolean hasAttributes(); /** - Checks if this node has a parent. Nodes won't have parents if e.g. they are newly created and not added as a child - to an exsiting node, or if they are a {@link #shallowClone()}. In such cases, {@link #parent()} will return {@code null}. + Checks if this node has a parent. Nodes won't have parents if (e.g.) they are newly created and not added as a child + to an existing node, or if they are a {@link #shallowClone()}. In such cases, {@link #parent()} will return {@code null}. @return if this node has a parent. */ public boolean hasParent() { @@ -145,8 +145,11 @@ public Node clearAttributes() { } /** - Get the base URI that applies to this node. Empty string if not defined. Used to make relative links absolute to. + Get the base URI that applies to this node. Will return an empty string if not defined. Used to make relative links + absolute. + @return base URI + @see #absUrl */ public abstract String baseUri(); @@ -166,7 +169,7 @@ public void setBaseUri(final String baseUri) { } /** - * Get an absolute URL from a URL attribute that may be relative (i.e. an <code>&lt;a href&gt;</code> or + * Get an absolute URL from a URL attribute that may be relative (such as an <code>&lt;a href&gt;</code> or * <code>&lt;img src&gt;</code>). * <p> * E.g.: <code>String absUrl = linkEl.absUrl("href");</code> @@ -301,7 +304,7 @@ public void remove() { } /** - * Insert the specified HTML into the DOM before this node (i.e. as a preceding sibling). + * Insert the specified HTML into the DOM before this node (as a preceding sibling). * @param html HTML to add before this node * @return this node, for chaining * @see #after(String) @@ -312,7 +315,7 @@ public Node before(String html) { } /** - * Insert the specified node into the DOM before this node (i.e. as a preceding sibling). + * Insert the specified node into the DOM before this node (as a preceding sibling). * @param node to add before this node * @return this node, for chaining * @see #after(Node) @@ -326,7 +329,7 @@ public Node before(Node node) { } /** - * Insert the specified HTML into the DOM after this node (i.e. as a following sibling). + * Insert the specified HTML into the DOM after this node (as a following sibling). * @param html HTML to add after this node * @return this node, for chaining * @see #before(String) @@ -337,7 +340,7 @@ public Node after(String html) { } /** - * Insert the specified node into the DOM after this node (i.e. as a following sibling). + * Insert the specified node into the DOM after this node (as a following sibling). * @param node to add after this node * @return this node, for chaining * @see #before(Node) @@ -588,7 +591,7 @@ public List<Node> siblingNodes() { } /** - * Get the list index of this node in its node sibling list. I.e. if this is the first node + * Get the list index of this node in its node sibling list. E.g. if this is the first node * sibling, returns 0. * @return position in node sibling list * @see org.jsoup.nodes.Element#elementSiblingIndex() From 21b01b258952fd6aeb55d061f98321e5b9fe93e0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 3 Jan 2021 13:53:16 +1100 Subject: [PATCH 496/774] Added support for Internationalized Domain Names (IDNs) Fixes #1300 --- CHANGES | 3 +++ .../java/org/jsoup/helper/HttpConnection.java | 22 ++++++++++++++++++- .../java/org/jsoup/internal/StringUtil.java | 16 ++++++++++++++ .../org/jsoup/helper/HttpConnectionTest.java | 15 +++++++++++++ .../org/jsoup/integration/UrlConnectTest.java | 8 +++++++ .../org/jsoup/internal/StringUtilTest.java | 10 +++++++++ .../java/org/jsoup/nodes/ElementTest.java | 19 +++++++++++++++- 7 files changed, 91 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index cdb34be2c4..f281c161c6 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,9 @@ jsoup changelog jsoup 1.15.1 release. <https://github.com/jhy/jsoup/pull/1464> + * Improvement: added support for Internationalized Domain Names (IDNs) in Jsoup.Connect. + <https://github.com/jhy/jsoup/issues/1300> + * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri). * Improvement: reduced thread contention in HttpConnection and Document. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 6297feb27f..b5cf831739 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -21,6 +21,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; +import java.net.IDN; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Proxy; @@ -99,6 +100,7 @@ private static String encodeUrl(String url) { } static URL encodeUrl(URL u) { + u = punyUrl(u); try { // odd way to encode urls, but it works! String urlS = u.toExternalForm(); // URL external form may have spaces which is illegal in new URL() (odd asymmetry) @@ -111,6 +113,24 @@ static URL encodeUrl(URL u) { } } + /** + Convert an International URL to a Punycode URL. + @param url input URL that may include an international hostname + @return a punycode URL if required, or the original URL + */ + private static URL punyUrl(URL url) { + if (!StringUtil.isAscii(url.getHost())) { + try { + String puny = IDN.toASCII(url.getHost()); + url = new URL(url.getProtocol(), puny, url.getPort(), url.getFile()); // file will include ref, query if any + } catch (MalformedURLException e) { + // if passed a valid URL initially, cannot happen + throw new IllegalArgumentException(e); + } + } + return url; + } + private static String encodeMimeName(String val) { return val.replace("\"", "%22"); } @@ -339,7 +359,7 @@ public URL url() { public T url(URL url) { Validate.notNull(url, "URL must not be null"); - this.url = url; + this.url = punyUrl(url); // if calling url(url) directly, does not go through encodeUrl, so we punycode it explicitly. todo - should we encode here as well? return (T) this; } diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index a3ad0d316e..c6b8405f62 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -191,6 +191,22 @@ public static boolean inSorted(String needle, String[] haystack) { return Arrays.binarySearch(haystack, needle) >= 0; } + /** + Tests that a String contains only ASCII characters. + @param string scanned string + @return true if all characters are in range 0 - 127 + */ + public static boolean isAscii(String string) { + Validate.notNull(string); + for (int i = 0; i < string.length(); i++) { + int c = string.charAt(i); + if (c > 127) { // ascii range + return false; + } + } + return true; + } + /** * Create a new absolute URL, from a provided existing absolute URL and a relative URL component. * @param base the existing absolute base URL diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 2b85b92193..fad0710b79 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -1,8 +1,11 @@ package org.jsoup.helper; import org.jsoup.Connection; +import org.jsoup.Jsoup; import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.integration.ParseTest; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -254,4 +257,16 @@ public void caseInsensitiveHeaders(Locale locale) { Connection.Request req = new HttpConnection.Request(); req.addHeader("xxx", "é"); } + + @Test public void supportsInternationalDomainNames() throws MalformedURLException { + String idn = "https://www.测试.测试/foo.html?bar"; + String puny = "https://www.xn--0zwm56d.xn--0zwm56d/foo.html?bar"; + + Connection con = Jsoup.connect(idn); + assertEquals(puny, con.request().url().toExternalForm()); + + HttpConnection.Request req = new HttpConnection.Request(); + req.url(new URL(idn)); + assertEquals(puny, req.url().toExternalForm()); + } } diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index 1db78c4b2f..aeba4a3326 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -494,4 +494,12 @@ public void inWildUtfRedirect2() throws IOException { assertEquals("石嘴山市环境保护局", doc.title()); } + @Test public void canRequestIdn() throws IOException { + String url = "https://xn--rksmrgs-5wao1o.josefsson.org/"; + Document doc = Jsoup.connect(url).get(); + + assertEquals("https://xn--rksmrgs-5wao1o.josefsson.org/", doc.location()); + assertTrue(doc.title().contains("Räksmörgås.josefßon.org")); + } + } diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index f53cb8448d..55af0ff836 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -100,4 +100,14 @@ public void join() { assertEquals("ftp://example.com/one/two.c", resolve("ftp://example.com/one/", "./two.c")); assertEquals("ftp://example.com/one/two.c", resolve("ftp://example.com/one/", "two.c")); } + + @Test + void isAscii() { + assertTrue(StringUtil.isAscii("")); + assertTrue(StringUtil.isAscii("example.com")); + assertTrue(StringUtil.isAscii("One Two")); + assertFalse(StringUtil.isAscii("🧔")); + assertFalse(StringUtil.isAscii("测试")); + assertFalse(StringUtil.isAscii("测试.com")); + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 6913ff9e8c..abf060e317 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1179,7 +1179,7 @@ public void testHashAndEqualsAndValue() { @Test public void testRelativeUrls() { - String html = "<body><a href='./one.html'>One</a> <a href='two.html'>two</a> <a href='../three.html'>Three</a> <a href='//example2.com/four/'>Four</a> <a href='https://example2.com/five/'>Five</a>"; + String html = "<body><a href='./one.html'>One</a> <a href='two.html'>two</a> <a href='../three.html'>Three</a> <a href='//example2.com/four/'>Four</a> <a href='https://example2.com/five/'>Five</a> <a>Six</a> <a href=''>Seven</a>"; Document doc = Jsoup.parse(html, "http://example.com/bar/"); Elements els = doc.select("a"); @@ -1188,6 +1188,23 @@ public void testRelativeUrls() { assertEquals("http://example.com/three.html", els.get(2).absUrl("href")); assertEquals("http://example2.com/four/", els.get(3).absUrl("href")); assertEquals("https://example2.com/five/", els.get(4).absUrl("href")); + assertEquals("", els.get(5).absUrl("href")); + assertEquals("http://example.com/bar/", els.get(6).absUrl("href")); + } + + @Test + public void testRelativeIdnUrls() { + String idn = "https://www.测试.测试/"; + String idnFoo = idn + "foo.html?bar"; + + Document doc = Jsoup.parse("<a href=''>One</a><a href='/bar.html?qux'>Two</a>", idnFoo); + Elements els = doc.select("a"); + Element one = els.get(0); + Element two = els.get(1); + String hrefOne = one.absUrl("href"); + String hrefTwo = two.absUrl("href"); + assertEquals(idnFoo, hrefOne); + assertEquals("https://www.测试.测试/bar.html?qux", hrefTwo); } @Test From f104b5ad54eeeee2eae319242e5f3ccd92c7deb9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 4 Jan 2021 10:47:46 +1100 Subject: [PATCH 497/774] Corrected IDN host (I didn't notice that when you C&P an IDN URL from Chrome, Chrome places the punycode version on the clipboard.) --- src/test/java/org/jsoup/integration/UrlConnectTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/integration/UrlConnectTest.java b/src/test/java/org/jsoup/integration/UrlConnectTest.java index aeba4a3326..1a25206b8e 100644 --- a/src/test/java/org/jsoup/integration/UrlConnectTest.java +++ b/src/test/java/org/jsoup/integration/UrlConnectTest.java @@ -495,7 +495,7 @@ public void inWildUtfRedirect2() throws IOException { } @Test public void canRequestIdn() throws IOException { - String url = "https://xn--rksmrgs-5wao1o.josefsson.org/"; + String url = "https://räksmörgås.josefsson.org/"; Document doc = Jsoup.connect(url).get(); assertEquals("https://xn--rksmrgs-5wao1o.josefsson.org/", doc.location()); From dd054bb724f25f2ecfaa6fd9e6c1ce51186d0d66 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 4 Jan 2021 20:39:44 +1100 Subject: [PATCH 498/774] Testcase for selecting direct children Closes #984 --- src/test/java/org/jsoup/select/SelectorTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 2602c4b546..e691cc10ca 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -979,4 +979,20 @@ public void childElements() { assertEquals(outer, span); assertNotEquals(outer, inner); } + + @Test + public void selectFirstLevelChildrenOnly() { + // testcase for https://github.com/jhy/jsoup/issues/984 + String html = "<div><span>One <span>Two</span></span> <span>Three <span>Four</span></span>"; + Document doc = Jsoup.parse(html); + + Element div = doc.selectFirst("div"); + assertNotNull(div); + + // want to select One and Three only - the first level children + Elements spans = div.select(":root > span"); + assertEquals(2, spans.size()); + assertEquals("One Two", spans.get(0).text()); + assertEquals("Three Four", spans.get(1).text()); + } } From 7cf9d03bf311494df652a05c2528eb06b2373f9d Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 4 Jan 2021 21:08:29 +1100 Subject: [PATCH 499/774] In Element#cssSelector, check that IDs are unique before using them Fixes #1085 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Element.java | 14 ++++++++++++-- src/test/java/org/jsoup/nodes/ElementTest.java | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index f281c161c6..6166cd62f2 100644 --- a/CHANGES +++ b/CHANGES @@ -85,6 +85,10 @@ jsoup changelog * Bugfix: the Elements#forms() method should return the selected immediate elements that are Forms, not children. <https://github.com/jhy/jsoup/pull/1403> + * Bugfix: when creating a selector for an element with Element#cssSelector, if the element used a non-unique ID + attribute, the returned selector may not match the desired element. + <https://github.com/jhy/jsoup/issues/1085> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 6cbbf4e938..2b9cffaceb 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -772,8 +772,18 @@ public Element wrap(String html) { * @return the CSS Path that can be used to retrieve the element in a selector. */ public String cssSelector() { - if (id().length() > 0) - return "#" + id(); + if (id().length() > 0) { + // prefer to return the ID - but check that it's actually unique first! + String idSel = "#" + id(); + Document doc = ownerDocument(); + if (doc != null) { + Elements els = doc.select(idSel); + if (els.size() == 1 && els.get(0) == this) // otherwise, continue to the nth-child impl + return idSel; + } else { + return idSel; // no ownerdoc, return the ID selector + } + } // Translate HTML namespace ns:tag to CSS namespace syntax ns|tag String tagName = tagName().replace(':', '|'); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index abf060e317..996d016316 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1091,6 +1091,23 @@ public void testCssPath() { assertSame(divC, doc.select(divC.cssSelector()).first()); } + @Test + public void testCssPathDuplicateIds() { + // https://github.com/jhy/jsoup/issues/1147 - multiple elements with same ID, use the non-ID form + Document doc = Jsoup.parse("<article><div id=dupe>A</div><div id=dupe>B</div><div id=dupe class=c1>"); + Element divA = doc.select("div").get(0); + Element divB = doc.select("div").get(1); + Element divC = doc.select("div").get(2); + + assertEquals(divA.cssSelector(), "html > body > article > div:nth-child(1)"); + assertEquals(divB.cssSelector(), "html > body > article > div:nth-child(2)"); + assertEquals(divC.cssSelector(), "html > body > article > div.c1"); + + assertSame(divA, doc.select(divA.cssSelector()).first()); + assertSame(divB, doc.select(divB.cssSelector()).first()); + assertSame(divC, doc.select(divC.cssSelector()).first()); + } + @Test public void testClassNames() { Document doc = Jsoup.parse("<div class=\"c1 c2\">C</div>"); From 8e7dedbb257d2080b058cb732d068114d4b2bb67 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 5 Jan 2021 11:01:54 +1100 Subject: [PATCH 500/774] Testcase to demonstrate tab retention #1240 --- .../java/org/jsoup/parser/HtmlParserTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 36d4a31085..9f8bb6f0ad 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1378,4 +1378,23 @@ public void testUNewlines() { doc.outputSettings().prettyPrint(false); assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One <p>Hello!</p><p>There</p></body></html> ", doc.outerHtml()); } + + @Test public void preservesTabs() { + // testcase to demonstrate tab retention - https://github.com/jhy/jsoup/issues/1240 + String html = "<pre>One\tTwo</pre><span>\tThree\tFour</span>"; + Document doc = Jsoup.parse(html); + + Element pre = doc.selectFirst("pre"); + Element span = doc.selectFirst("span"); + + assertEquals("One\tTwo", pre.text()); + assertEquals("Three Four", span.text()); // normalized, including overall trim + assertEquals("\tThree\tFour", span.wholeText()); // text normalizes, wholeText retains original spaces incl tabs + assertEquals("One\tTwo Three Four", doc.body().text()); + + assertEquals("<pre>One\tTwo</pre><span> Three Four</span>", doc.body().html()); // html output provides normalized space, incl tab in pre but not in span + + doc.outputSettings().prettyPrint(false); + assertEquals(html, doc.body().html()); // disabling pretty-printing - round-trips the tab throughout, as no normalization occurs + } } From 4861505419b35b5373d00840b3f06775ccbf1454 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 5 Jan 2021 19:02:43 +1100 Subject: [PATCH 501/774] Fixes the toString() methods of the Evaluators --- CHANGES | 2 ++ .../org/jsoup/select/CombiningEvaluator.java | 4 ++-- .../java/org/jsoup/select/QueryParser.java | 15 +++++++++---- .../org/jsoup/select/StructuralEvaluator.java | 10 ++++----- .../org/jsoup/select/QueryParserTest.java | 22 +++++++++++++------ .../java/org/jsoup/select/SelectorTest.java | 1 + 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 6166cd62f2..b589958a1d 100644 --- a/CHANGES +++ b/CHANGES @@ -134,6 +134,8 @@ jsoup changelog * Bugfix: ensure that script and style contents are parsed into DataNodes, not TextNodes, when in case-sensitive parse mode. + * Bugfix: corrected the toString() methods of the Evaluator classes. + **** Release 1.12.2 [2020-Feb-08] * Improvement: the :has() selector now supports relative selectors. For example, the query diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index 8974a38bfd..9648e942af 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -50,7 +50,7 @@ static final class And extends CombiningEvaluator { @Override public boolean matches(Element root, Element node) { - for (int i = 0; i < num; i++) { + for (int i = num - 1; i >= 0; i--) { // process backwards so that :matchText is evaled earlier, to catch parent query. todo - should redo matchText to virtually expand during match, not pre-match (see SelectorTest#findBetweenSpan) Evaluator s = evaluators.get(i); if (!s.matches(root, node)) return false; @@ -60,7 +60,7 @@ public boolean matches(Element root, Element node) { @Override public String toString() { - return StringUtil.join(evaluators, " "); + return StringUtil.join(evaluators, ""); } } diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 57636b9be8..ddd52bf3cf 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -106,13 +106,13 @@ private void combinator(char combinator) { // for most combinators: change the current eval into an AND of the current eval and the new eval if (combinator == '>') - currentEval = new CombiningEvaluator.And(newEval, new StructuralEvaluator.ImmediateParent(currentEval)); + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.ImmediateParent(currentEval), newEval); else if (combinator == ' ') - currentEval = new CombiningEvaluator.And(newEval, new StructuralEvaluator.Parent(currentEval)); + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.Parent(currentEval), newEval); else if (combinator == '+') - currentEval = new CombiningEvaluator.And(newEval, new StructuralEvaluator.ImmediatePreviousSibling(currentEval)); + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.ImmediatePreviousSibling(currentEval), newEval); else if (combinator == '~') - currentEval = new CombiningEvaluator.And(newEval, new StructuralEvaluator.PreviousSibling(currentEval)); + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.PreviousSibling(currentEval), newEval); else if (combinator == ',') { // group or. CombiningEvaluator.Or or; if (currentEval instanceof CombiningEvaluator.Or) { @@ -382,4 +382,11 @@ private void not() { evals.add(new StructuralEvaluator.Not(parse(subQuery))); } + + @Override + public String toString() { + return query; + } + + } diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index 979e40c0b5..d35df3c1e0 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -44,7 +44,7 @@ public boolean matches(Element root, Element node) { @Override public String toString() { - return String.format(":not%s", evaluator); + return String.format(":not(%s)", evaluator); } } @@ -70,7 +70,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":parent%s", evaluator); + return String.format("%s ", evaluator); } } @@ -89,7 +89,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":ImmediateParent%s", evaluator); + return String.format("%s > ", evaluator); } } @@ -115,7 +115,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":prev*%s", evaluator); + return String.format("%s ~ ", evaluator); } } @@ -134,7 +134,7 @@ public boolean matches(Element root, Element element) { @Override public String toString() { - return String.format(":prev%s", evaluator); + return String.format("%s + ", evaluator); } } } diff --git a/src/test/java/org/jsoup/select/QueryParserTest.java b/src/test/java/org/jsoup/select/QueryParserTest.java index 95c4da16a2..34ee8a9fcc 100644 --- a/src/test/java/org/jsoup/select/QueryParserTest.java +++ b/src/test/java/org/jsoup/select/QueryParserTest.java @@ -21,13 +21,14 @@ public class QueryParserTest { assertTrue(innerEval instanceof CombiningEvaluator.And); CombiningEvaluator.And and = (CombiningEvaluator.And) innerEval; assertEquals(2, and.evaluators.size()); - assertTrue(and.evaluators.get(0) instanceof Evaluator.Tag); - assertTrue(and.evaluators.get(1) instanceof StructuralEvaluator.Parent); + assertTrue(and.evaluators.get(0) instanceof StructuralEvaluator.Parent); + assertTrue(and.evaluators.get(1) instanceof Evaluator.Tag); } } @Test public void testParsesMultiCorrectly() { - Evaluator eval = QueryParser.parse(".foo > ol, ol > li + li"); + String query = ".foo > ol, ol > li + li"; + Evaluator eval = QueryParser.parse(query); assertTrue(eval instanceof CombiningEvaluator.Or); CombiningEvaluator.Or or = (CombiningEvaluator.Or) eval; assertEquals(2, or.evaluators.size()); @@ -35,10 +36,11 @@ public class QueryParserTest { CombiningEvaluator.And andLeft = (CombiningEvaluator.And) or.evaluators.get(0); CombiningEvaluator.And andRight = (CombiningEvaluator.And) or.evaluators.get(1); - assertEquals("ol :ImmediateParent.foo", andLeft.toString()); - assertEquals(2, andLeft.evaluators.size()); - assertEquals("li :prevli :ImmediateParentol", andRight.toString()); + assertEquals(".foo > ol", andLeft.toString()); assertEquals(2, andLeft.evaluators.size()); + assertEquals("ol > li + li", andRight.toString()); + assertEquals(2, andRight.evaluators.size()); + assertEquals(query, eval.toString()); } @Test public void exceptionOnUncloseAttribute() { @@ -60,6 +62,12 @@ public class QueryParserTest { @Test public void okOnSpacesForeAndAft() { Evaluator parse = QueryParser.parse(" span div "); - assertEquals("div :parentspan", parse.toString()); // TODO - don't really love that toString() result... + assertEquals("span div", parse.toString()); + } + + @Test public void structuralEvaluatorsToString() { + String q = "a:not(:has(span.foo)) b d > e + f ~ g"; + Evaluator parse = QueryParser.parse(q); + assertEquals(q, parse.toString()); } } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index e691cc10ca..ac46ee7fef 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -871,6 +871,7 @@ public void containsData(Locale locale) { @Test public void findBetweenSpan() { Document doc = Jsoup.parse("<p><span>One</span> Two <span>Three</span>"); Elements els = doc.select("span ~ p:matchText"); // the Two becomes its own p, sibling of the span + // todo - think this should really be 'p:matchText span ~ p'. The :matchText should behave as a modifier to expand the nodes. assertEquals(1, els.size()); assertEquals("Two", els.text()); From 42d77caa66546bfeb4451b0061c74569e3100815 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 5 Jan 2021 19:16:19 +1100 Subject: [PATCH 502/774] Use a switch jump table for combinator selection --- .../java/org/jsoup/select/QueryParser.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index ddd52bf3cf..f06d10d3fa 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -105,27 +105,33 @@ private void combinator(char combinator) { evals.clear(); // for most combinators: change the current eval into an AND of the current eval and the new eval - if (combinator == '>') - currentEval = new CombiningEvaluator.And(new StructuralEvaluator.ImmediateParent(currentEval), newEval); - else if (combinator == ' ') - currentEval = new CombiningEvaluator.And(new StructuralEvaluator.Parent(currentEval), newEval); - else if (combinator == '+') - currentEval = new CombiningEvaluator.And(new StructuralEvaluator.ImmediatePreviousSibling(currentEval), newEval); - else if (combinator == '~') - currentEval = new CombiningEvaluator.And(new StructuralEvaluator.PreviousSibling(currentEval), newEval); - else if (combinator == ',') { // group or. - CombiningEvaluator.Or or; - if (currentEval instanceof CombiningEvaluator.Or) { - or = (CombiningEvaluator.Or) currentEval; - } else { - or = new CombiningEvaluator.Or(); - or.add(currentEval); - } - or.add(newEval); - currentEval = or; + switch (combinator) { + case '>': + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.ImmediateParent(currentEval), newEval); + break; + case ' ': + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.Parent(currentEval), newEval); + break; + case '+': + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.ImmediatePreviousSibling(currentEval), newEval); + break; + case '~': + currentEval = new CombiningEvaluator.And(new StructuralEvaluator.PreviousSibling(currentEval), newEval); + break; + case ',': + CombiningEvaluator.Or or; + if (currentEval instanceof CombiningEvaluator.Or) { + or = (CombiningEvaluator.Or) currentEval; + } else { + or = new CombiningEvaluator.Or(); + or.add(currentEval); + } + or.add(newEval); + currentEval = or; + break; + default: + throw new Selector.SelectorParseException("Unknown combinator: " + combinator); } - else - throw new Selector.SelectorParseException("Unknown combinator: " + combinator); if (replaceRightMost) ((CombiningEvaluator.Or) rootEval).replaceRightMostEvaluator(currentEval); From 165b3c8225eaed0dd3bff1284375b1ff4340c9ad Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 6 Jan 2021 12:18:55 +1100 Subject: [PATCH 503/774] In W3CDom, treat illegal tag names as text Nodes Fixes #1093 --- CHANGES | 4 ++ pom.xml | 2 +- src/main/java/org/jsoup/helper/W3CDom.java | 40 ++++++++++++------- .../java/org/jsoup/helper/W3CDomTest.java | 14 ++++++- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index b589958a1d..92ee2d8e56 100644 --- a/CHANGES +++ b/CHANGES @@ -136,6 +136,10 @@ jsoup changelog * Bugfix: corrected the toString() methods of the Evaluator classes. + * Bugfix: when converting a jsoup document to a W3C document (in W3CDom#convert), if a tag had XML illegal characters, + a DOMException would be thown. Now instead, that tag is represented as a text node. + <https://github.com/jhy/jsoup/issues/1093> + **** Release 1.12.2 [2020-Feb-08] * Improvement: the :has() selector now supports relative selectors. For example, the query diff --git a/pom.xml b/pom.xml index 105b546f9b..af898cb87b 100644 --- a/pom.xml +++ b/pom.xml @@ -200,7 +200,7 @@ </oldVersion> <parameter> <!-- jsoup policy is ok to remove deprecated methods on minor but not builds. will need to temp remove on bump to 1.15.1 and manually validate --> - <onlyModified>true</onlyModified> + <onlyModified>false</onlyModified> <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> </parameter> diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 324a0d5557..829c8fa9f5 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -6,12 +6,15 @@ import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import org.w3c.dom.Comment; +import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.w3c.dom.Text; +import javax.annotation.Nullable; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -26,6 +29,7 @@ import java.util.Map; import java.util.Properties; import java.util.Stack; +import java.util.regex.Pattern; import static javax.xml.transform.OutputKeys.METHOD; @@ -72,7 +76,7 @@ public static Document convert(org.jsoup.nodes.Document in) { * @see OutputKeys#INDENT * @see OutputKeys#MEDIA_TYPE */ - public static String asString(Document doc, Map<String, String> properties) { + public static String asString(Document doc, @Nullable Map<String, String> properties) { try { DOMSource domSource = new DOMSource(doc); StringWriter writer = new StringWriter(); @@ -190,11 +194,12 @@ protected static class W3CBuilder implements NodeVisitor { private final Document doc; private final Stack<HashMap<String, String>> namespacesStack = new Stack<>(); // stack of namespaces, prefix => urn - private Element dest; + private Node dest; public W3CBuilder(Document doc) { this.doc = doc; - this.namespacesStack.push(new HashMap<String, String>()); + this.namespacesStack.push(new HashMap<>()); + this.dest = doc; } public void head(org.jsoup.nodes.Node source, int depth) { @@ -206,16 +211,20 @@ public void head(org.jsoup.nodes.Node source, int depth) { String namespace = namespacesStack.peek().get(prefix); String tagName = sourceEl.tagName(); - Element el = namespace == null && tagName.contains(":") ? - doc.createElementNS("", tagName) : // doesn't have a real namespace defined - doc.createElementNS(namespace, tagName); - copyAttributes(sourceEl, el); - if (dest == null) { // sets up the root - doc.appendChild(el); - } else { + /* Tag names in XML are quite, but less, permissive than HTML. Rather than reimplement the validation, + we just try to use it as-is. If it fails, insert as a text node instead. We don't try to normalize the + tagname to something safe, because that isn't going to be meaningful downstream. This seems(?) to be + how browsers handle the situation, also. https://github.com/jhy/jsoup/issues/1093 */ + try { + Element el = namespace == null && tagName.contains(":") ? + doc.createElementNS("", tagName) : // doesn't have a real namespace defined + doc.createElementNS(namespace, tagName); + copyAttributes(sourceEl, el); dest.appendChild(el); + dest = el; // descend + } catch (DOMException e) { + dest.appendChild(doc.createTextNode("<" + tagName + ">")); } - dest = el; // descend } else if (source instanceof org.jsoup.nodes.TextNode) { org.jsoup.nodes.TextNode sourceText = (org.jsoup.nodes.TextNode) source; Text text = doc.createTextNode(sourceText.getWholeText()); @@ -236,16 +245,19 @@ public void head(org.jsoup.nodes.Node source, int depth) { public void tail(org.jsoup.nodes.Node source, int depth) { if (source instanceof org.jsoup.nodes.Element && dest.getParentNode() instanceof Element) { - dest = (Element) dest.getParentNode(); // undescend. cromulent. + dest = dest.getParentNode(); // undescend. cromulent. } namespacesStack.pop(); } + private static final Pattern attrKeyReplace = Pattern.compile("[^-a-zA-Z0-9_:.]"); + private static final Pattern attrKeyValid = Pattern.compile("[a-zA-Z_:][-a-zA-Z0-9_:.]*"); + private void copyAttributes(org.jsoup.nodes.Node source, Element el) { for (Attribute attribute : source.attributes()) { // valid xml attribute names are: ^[a-zA-Z_:][-a-zA-Z0-9_:.] - String key = attribute.getKey().replaceAll("[^-a-zA-Z0-9_:.]", ""); - if (key.matches("[a-zA-Z_:][-a-zA-Z0-9_:.]*")) + String key = attrKeyReplace.matcher(attribute.getKey()).replaceAll(""); + if (attrKeyValid.matcher(key).matches()) el.setAttribute(key, attribute.getValue()); } } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 02bf00fe81..c2253bf5e7 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -183,7 +183,18 @@ public void handlesInvalidAttributeNames() { assertTrue(body.hasAttr("\"")); // actually an attribute with key '"'. Correct per HTML5 spec, but w3c xml dom doesn't dig it assertTrue(body.hasAttr("name\"")); - Document w3Doc = new W3CDom().fromJsoup(jsoupDoc); + Document w3Doc = W3CDom.convert(jsoupDoc); + String xml = W3CDom.asString(w3Doc, W3CDom.OutputXml()); + assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><head/><body name=\"\" style=\"color: red\"/></html>", xml); + } + + @Test + public void handlesInvalidTagAsText() { + org.jsoup.nodes.Document jsoup = Jsoup.parse("<インセンティブで高収入!>Text <p>More</p>"); + + Document w3Doc = W3CDom.convert(jsoup); + String xml = W3CDom.asString(w3Doc, W3CDom.OutputXml()); + assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><head/><body>&lt;インセンティブで高収入!&gt;Text <p>More</p></body></html>", xml); } @Test @@ -280,4 +291,3 @@ private String output(String in, boolean modeHtml) { } } - From 40a7d354d59bdf3d22c09586a3ea7d85b22b9ea1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 6 Jan 2021 13:01:41 +1100 Subject: [PATCH 504/774] Corrected changelog order --- CHANGES | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 92ee2d8e56..242d41c719 100644 --- a/CHANGES +++ b/CHANGES @@ -89,6 +89,12 @@ jsoup changelog attribute, the returned selector may not match the desired element. <https://github.com/jhy/jsoup/issues/1085> + * Bugfix: corrected the toString() methods of the Evaluator classes. + + * Bugfix: when converting a jsoup document to a W3C document (in W3CDom#convert), if a tag had XML illegal characters, + a DOMException would be thown. Now instead, that tag is represented as a text node. + <https://github.com/jhy/jsoup/issues/1093> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. @@ -134,13 +140,6 @@ jsoup changelog * Bugfix: ensure that script and style contents are parsed into DataNodes, not TextNodes, when in case-sensitive parse mode. - * Bugfix: corrected the toString() methods of the Evaluator classes. - - * Bugfix: when converting a jsoup document to a W3C document (in W3CDom#convert), if a tag had XML illegal characters, - a DOMException would be thown. Now instead, that tag is represented as a text node. - <https://github.com/jhy/jsoup/issues/1093> - - **** Release 1.12.2 [2020-Feb-08] * Improvement: the :has() selector now supports relative selectors. For example, the query "div:has(> a)" will select all "div" elements that have at least one direct child "a" element. From ec03d27474df95abcf36e038e1c98997f57096e0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 6 Jan 2021 14:57:15 +1100 Subject: [PATCH 505/774] Dont emit "EOF" character tokens Was occuring due to casting an EOF token to a string (from #540). --- CHANGES | 2 ++ src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 3 +++ src/main/java/org/jsoup/parser/Token.java | 9 ++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 242d41c719..6616cea12b 100644 --- a/CHANGES +++ b/CHANGES @@ -95,6 +95,8 @@ jsoup changelog a DOMException would be thown. Now instead, that tag is represented as a text node. <https://github.com/jhy/jsoup/issues/1093> + * Bugfix: if a HTML file ended with an open noscript tag, an "EOF" string would appear in the HTML output. + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 0d660d197b..cab017a171 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -195,6 +195,9 @@ boolean process(Token t, HtmlTreeBuilder tb) { } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { + // note that this deviates from spec, which is to pop out of noscript and reprocess in head: + // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inheadnoscript + // allows content to be inserted as data tb.error(this); tb.insert(new Token.Character().data(t.toString())); return true; diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 4cdde952ca..78318d8d48 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -219,6 +219,9 @@ private void ensureAttributeValue() { pendingAttributeValueS = null; } } + + @Override + abstract public String toString(); } final static class StartTag extends Tag { @@ -307,7 +310,6 @@ private void ensureData() { } } - @Override public String toString() { return "<!--" + getData() + "-->"; @@ -365,6 +367,11 @@ final static class EOF extends Token { Token reset() { return this; } + + @Override + public String toString() { + return ""; + } } final boolean isDoctype() { From 90fd58cdaee35d4e9b4b9a405bf155be12b02b0b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 7 Jan 2021 11:27:43 +1100 Subject: [PATCH 506/774] Testcase for noscript EOF token --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 9f8bb6f0ad..0003ff2ab5 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -615,6 +615,15 @@ public class HtmlParserTest { assertEquals("<html><head><noscript>&lt;img src=\"foo\"&gt;</noscript> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>Hello</p></body></html>", TextUtil.stripNewlines(doc.html())); } + @Test public void testUnclosedNoscriptInHead() { + // Was getting "EOF" in html output, because the #anythingElse handler was calling an undefined toString, so used object.toString. + String[] strings = {"<noscript>", "<noscript>One"}; + for (String html : strings) { + Document doc = Jsoup.parse(html); + assertEquals(html + "</noscript>", TextUtil.stripNewlines(doc.head().html())); + } + } + @Test public void testAFlowContents() { // html5 has <a> as either phrasing or block Document doc = Jsoup.parse("<a>Hello <div>there</div> <span>now</span></a>"); From 78e14c4b7bd13baef6f153ecbcc91e21d2f929a3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 7 Jan 2021 11:29:38 +1100 Subject: [PATCH 507/774] =?UTF-8?q?=E2=9C=A8=20Happy=20New=20Year=202021?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 1802c6faeb..d704b1265e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2009-2020 Jonathan Hedley <https://jsoup.org/> +Copyright (c) 2009-2021 Jonathan Hedley <https://jsoup.org/> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 0802228b14ca2bf411bd06c69dc562d0b8e96a36 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 7 Jan 2021 17:04:16 +1100 Subject: [PATCH 508/774] Fixed dangling brace --- src/main/java/org/jsoup/nodes/Element.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 2b9cffaceb..79839b68a8 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1320,7 +1320,7 @@ static boolean preserveWhitespace(@Nullable Node node) { /** * Set the text of this element. Any existing contents (text or elements) will be cleared. - * <p>As a special case, for {@code <script>} and {@code <style> tags, the input text will be treated as data, + * <p>As a special case, for {@code <script>} and {@code <style>} tags, the input text will be treated as data, * not visible text.</p> * @param text unencoded text * @return this element From 5174584e4d52135e82652d021aa2faf64bf31074 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 8 Jan 2021 11:05:18 +1100 Subject: [PATCH 509/774] Simple test to detect if a document was normalized Example code for https://stackoverflow.com/a/65612378/153184 --- .../java/org/jsoup/parser/HtmlParserTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 0003ff2ab5..7f2ff97fa6 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1406,4 +1406,22 @@ public void testUNewlines() { doc.outputSettings().prettyPrint(false); assertEquals(html, doc.body().html()); // disabling pretty-printing - round-trips the tab throughout, as no normalization occurs } + + @Test public void canDetectAutomaticallyAddedElements() { + String bare = "<script>One</script>"; + String full = "<html><head><title>Check</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p></body></html>"; + + assertTrue(didAddElements(bare)); + assertFalse(didAddElements(full)); + } + + private boolean didAddElements(String input) { + // two passes, one as XML and one as HTML. XML does not vivify missing/optional tags + Document html = Jsoup.parse(input); + Document xml = Jsoup.parse(input, "", Parser.xmlParser()); + + int htmlElementCount = html.getAllElements().size(); + int xmlElementCount = xml.getAllElements().size(); + return htmlElementCount > xmlElementCount; + } } From 1736e79861a2b17923e0b4e5b35ffccd729b3026 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 8 Jan 2021 12:31:33 +1100 Subject: [PATCH 510/774] Javadoc typo --- src/main/java/org/jsoup/nodes/Comment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index c966d77d6f..0ecfa3a1bf 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -67,7 +67,7 @@ public boolean isXmlDeclaration() { } /** - * Attempt to cast this comment to an XML Declaration note. + * Attempt to cast this comment to an XML Declaration node. * @return an XML declaration if it could be parsed as one, null otherwise. */ public @Nullable XmlDeclaration asXmlDeclaration() { From 7d4f963539e6206af7f79af5ef08c8fb7535746c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 9 Jan 2021 17:10:32 +1100 Subject: [PATCH 511/774] Update japicmp, and enable JDK 16-ea CI builds (#1475) --- .github/workflows/build.yml | 6 +----- pom.xml | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b618a49e8..f4f06da63b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time - java: [8, 11, 15] - # Temporarily removed 16-ea, as it started failing in the japicmp plugin phase. - # Working (with japacmp): https://github.com/jhy/jsoup/runs/1556442000?check_suite_focus=true - # Failing: https://github.com/jhy/jsoup/runs/1556669892?check_suite_focus=true - # Issue raised: https://github.com/siom79/japicmp/issues/275 + java: [8, 11, 15, 16-ea] fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: diff --git a/pom.xml b/pom.xml index af898cb87b..291566c983 100644 --- a/pom.xml +++ b/pom.xml @@ -187,7 +187,7 @@ <!-- API version compat check - https://siom79.github.io/japicmp/ --> <groupId>com.github.siom79.japicmp</groupId> <artifactId>japicmp-maven-plugin</artifactId> - <version>0.14.4</version> + <version>0.15.2</version> <configuration> <!-- hard code previous version; can't detect when running stateless on build server --> <oldVersion> From 55e5a4284e0b45f57380f062a933a746564b264a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 11 Jan 2021 17:29:37 +1100 Subject: [PATCH 512/774] Clarified the nullability annotations within Connection and HttpConnection --- pom.xml | 4 +- src/main/java/org/jsoup/Connection.java | 49 ++--- .../java/org/jsoup/helper/HttpConnection.java | 172 +++++++++++------- src/main/java/org/jsoup/helper/Validate.java | 14 +- .../internal/FieldsAreNonnullByDefault.java | 2 +- .../org/jsoup/internal/NonnullByDefault.java | 2 +- .../org/jsoup/helper/HttpConnectionTest.java | 30 ++- 7 files changed, 171 insertions(+), 102 deletions(-) diff --git a/pom.xml b/pom.xml index 291566c983..e1a2379538 100644 --- a/pom.xml +++ b/pom.xml @@ -325,11 +325,11 @@ </dependency> <dependency> - <!-- javax.annotations.nonnull, with Apache 2 (not GPL) license. Not distributed. --> + <!-- javax.annotations.nonnull, with Apache 2 (not GPL) license. Build time only. --> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> <version>3.0.2</version> - <scope>compile</scope> + <scope>provided</scope> </dependency> </dependencies> diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index d7c2df89a3..841e0e2726 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -15,7 +15,7 @@ import java.util.Map; /** - * A Connection provides a convenient interface to fetch content from the web, and parse them into Documents. + * The Connection interface is a convenient HTTP client to fetch content from the web, and parse them into Documents. * <p> * To get a new Connection, use {@link org.jsoup.Jsoup#connect(String)}. Connections contain {@link Connection.Request} * and {@link Connection.Response} objects. The request objects are reusable as prototype requests. @@ -26,6 +26,7 @@ * executed. * </p> */ +@SuppressWarnings("unused") public interface Connection { /** @@ -64,11 +65,11 @@ public final boolean hasBody() { Connection url(String url); /** - * Set the proxy to use for this request. Set to <code>null</code> to disable. + * Set the proxy to use for this request. Set to <code>null</code> to disable a previously set proxy. * @param proxy proxy to use * @return this Connection, for chaining */ - Connection proxy(Proxy proxy); + Connection proxy(@Nullable Proxy proxy); /** * Set the HTTP proxy to use for this request. @@ -326,8 +327,9 @@ public final boolean hasBody() { Connection request(Request request); /** - * Get the response, once the request has been executed + * Get the response, once the request has been executed. * @return response + * @throws IllegalArgumentException if called before the response has been executed. */ Response response(); @@ -342,13 +344,14 @@ public final boolean hasBody() { * Common methods for Requests and Responses * @param <T> Type of Base, either Request or Response */ - interface Base<T extends Base> { - + @SuppressWarnings("UnusedReturnValue") + interface Base<T extends Base<T>> { /** - * Get the URL - * @return URL, or {@code null} if it has not yet been set. + * Get the URL of this Request or Response. For redirected responses, this will be the final destination URL. + * @return URL + * @throws IllegalArgumentException if called on a Request that was created without a URL. */ - @Nullable URL url(); + URL url(); /** * Set the URL @@ -358,10 +361,10 @@ interface Base<T extends Base> { T url(URL url); /** - * Get the request method - * @return method, or {@code null} if it has not yet been set. + * Get the request method, which defaults to <code>GET</code> + * @return method */ - @Nullable Method method(); + Method method(); /** * Set the request method @@ -456,7 +459,7 @@ interface Base<T extends Base> { * @param name name of cookie to retrieve. * @return value of cookie, or null if not set */ - String cookie(String name); + @Nullable String cookie(String name); /** * Set a cookie in this request/response. @@ -490,6 +493,7 @@ interface Base<T extends Base> { /** * Represents a HTTP request. */ + @SuppressWarnings("UnusedReturnValue") interface Request extends Base<Request> { /** * Get the proxy used for this request. @@ -502,7 +506,7 @@ interface Request extends Base<Request> { * @param proxy the proxy ot use; <code>null</code> to disable. * @return this Request, for chaining */ - Request proxy(Proxy proxy); + Request proxy(@Nullable Proxy proxy); /** * Set the HTTP proxy to use for this request. @@ -583,7 +587,7 @@ interface Request extends Base<Request> { * Get the current custom SSL socket factory, if any. * @return custom SSL socket factory if set, null otherwise */ - SSLSocketFactory sslSocketFactory(); + @Nullable SSLSocketFactory sslSocketFactory(); /** * Set a custom SSL socket factory. @@ -612,15 +616,16 @@ interface Request extends Base<Request> { * .header("Content-Type", "application/json") * .post();</pre></code> * If any data key/vals are supplied, they will be sent as URL query params. + * @param body to use as the request body. Set to null to clear a previously set body. * @return this Request, for chaining */ - Request requestBody(String body); + Request requestBody(@Nullable String body); /** * Get the current request body. * @return null if not set. */ - String requestBody(); + @Nullable String requestBody(); /** * Specify the parser to use when parsing the document. @@ -669,9 +674,9 @@ interface Response extends Base<Response> { /** * Get the character set name of the response, derived from the content-type header. - * @return character set name + * @return character set name if set, <b>null</b> if not */ - String charset(); + @Nullable String charset(); /** * Set / override the response character set. When the document body is parsed it will be with this charset. @@ -682,9 +687,9 @@ interface Response extends Base<Response> { /** * Get the response content type (e.g. "text/html"); - * @return the response content type + * @return the response content type, or <b>null</b> if one was not set */ - String contentType(); + @Nullable String contentType(); /** * Read and parse the body of the response as a Document. If you intend to parse the same response multiple @@ -766,7 +771,7 @@ interface KeyVal { * Get the input stream associated with this keyval, if any * @return input stream if set, or null */ - InputStream inputStream(); + @Nullable InputStream inputStream(); /** * Does this keyval have an input stream? diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index b5cf831739..41565c6cc6 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -51,6 +51,7 @@ * Implementation of {@link Connection}. * @see org.jsoup.Jsoup#connect(String) */ +@SuppressWarnings("CharsetObjectCanBeUsed") public class HttpConnection implements Connection { public static final String CONTENT_ENCODING = "Content-Encoding"; /** @@ -80,9 +81,11 @@ public static Connection connect(URL url) { return con; } + /** + Creates a new, empty HttpConnection. + */ public HttpConnection() { req = new Request(); - res = new Response(); } /** @@ -136,7 +139,7 @@ private static String encodeMimeName(String val) { } private Connection.Request req; - private Connection.Response res; + private @Nullable Connection.Response res; public Connection url(URL url) { req.url(url); @@ -153,7 +156,7 @@ public Connection url(String url) { return this; } - public Connection proxy(Proxy proxy) { + public Connection proxy(@Nullable Proxy proxy) { req.proxy(proxy); return this; } @@ -304,12 +307,14 @@ public Connection parser(Parser parser) { public Document get() throws IOException { req.method(Method.GET); execute(); + Validate.notNull(res); return res.parse(); } public Document post() throws IOException { req.method(Method.POST); execute(); + Validate.notNull(res); return res.parse(); } @@ -328,6 +333,9 @@ public Connection request(Connection.Request request) { } public Connection.Response response() { + if (res == null) { + throw new IllegalArgumentException("You must execute the request before getting a response."); + } return res; } @@ -341,10 +349,20 @@ public Connection postDataCharset(String charset) { return this; } - @SuppressWarnings({"unchecked"}) - private static abstract class Base<T extends Connection.Base> implements Connection.Base<T> { - @Nullable URL url; - @Nullable Method method; + + @SuppressWarnings("unchecked") + private static abstract class Base<T extends Connection.Base<T>> implements Connection.Base<T> { + private static final URL UnsetUrl; // only used if you created a new Request() + static { + try { + UnsetUrl = new URL("http://undefined/"); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + } + + URL url = UnsetUrl; + Method method = Method.GET; Map<String, List<String>> headers; Map<String, String> cookies; @@ -354,6 +372,8 @@ private Base() { } public URL url() { + if (url == UnsetUrl) + throw new IllegalArgumentException("URL not yet set"); return url; } @@ -584,7 +604,7 @@ public Proxy proxy() { return proxy; } - public Request proxy(Proxy proxy) { + public Request proxy(@Nullable Proxy proxy) { this.proxy = proxy; return this; } @@ -659,7 +679,7 @@ public Collection<Connection.KeyVal> data() { return data; } - public Connection.Request requestBody(String body) { + public Connection.Request requestBody(@Nullable String body) { this.body = body; return this; } @@ -693,44 +713,44 @@ public String postDataCharset() { public static class Response extends HttpConnection.Base<Connection.Response> implements Connection.Response { private static final int MAX_REDIRECTS = 20; private static final String LOCATION = "Location"; - private int statusCode; - private String statusMessage; - private ByteBuffer byteData; - private InputStream bodyStream; - private HttpURLConnection conn; - private String charset; - private String contentType; + private final int statusCode; + private final String statusMessage; + private @Nullable ByteBuffer byteData; + private @Nullable InputStream bodyStream; + private @Nullable HttpURLConnection conn; + private @Nullable String charset; + private @Nullable final String contentType; private boolean executed = false; private boolean inputStreamRead = false; private int numRedirects = 0; - private Connection.Request req; + private final Connection.Request req; /* * Matches XML content types (like text/xml, application/xhtml+xml;charset=UTF8, etc) */ private static final Pattern xmlContentTypeRxp = Pattern.compile("(application|text)/\\w*\\+?xml.*"); + /** + <b>Internal only! </b>Creates a dummy HttpConnection.Response, useful for testing. All actual responses + are created from the HttpURLConnection and fields defined. + */ Response() { super(); - } - - private Response(Response previousResponse) throws IOException { - super(); - if (previousResponse != null) { - numRedirects = previousResponse.numRedirects + 1; - if (numRedirects >= MAX_REDIRECTS) - throw new IOException(String.format("Too many redirects occurred trying to load URL %s", previousResponse.url())); - } + statusCode = 400; + statusMessage = "Request not made"; + req = new Request(); + contentType = null; } static Response execute(Connection.Request req) throws IOException { return execute(req, null); } - static Response execute(Connection.Request req, Response previousResponse) throws IOException { + static Response execute(Connection.Request req, @Nullable Response previousResponse) throws IOException { Validate.notNull(req, "Request must not be null"); - Validate.notNull(req.url(), "URL must be specified to connect"); - String protocol = req.url().getProtocol(); + URL url = req.url(); + Validate.notNull(url, "URL must be specified to connect"); + String protocol = url.getProtocol(); if (!protocol.equals("http") && !protocol.equals("https")) throw new MalformedURLException("Only http & https protocols supported"); final boolean methodHasBody = req.method().hasBody(); @@ -754,9 +774,7 @@ else if (methodHasBody) writePost(req, conn.getOutputStream(), mimeBoundary); int status = conn.getResponseCode(); - res = new Response(previousResponse); - res.setupFromConnection(conn, previousResponse); - res.req = req; + res = new Response(conn, req, previousResponse); // redirect if there's a location header (from 3xx, or 201 etc) if (res.hasHeader(LOCATION) && req.followRedirects()) { @@ -768,6 +786,7 @@ else if (methodHasBody) } String location = res.header(LOCATION); + Validate.notNull(location); if (location.startsWith("http:/") && location.charAt(6) != '/') // fix broken Location: http:/temp/AAG_New/en/index.php location = location.substring(6); URL redir = StringUtil.resolve(req.url(), location); @@ -801,8 +820,8 @@ else if (methodHasBody) res.charset = DataUtil.getCharsetFromContentType(res.contentType); // may be null, readInputStream deals with it if (conn.getContentLength() != 0 && req.method() != HEAD) { // -1 means unknown, chunked. sun throws an IO exception on 500 response with no content when trying to read body - res.bodyStream = null; res.bodyStream = conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream(); + Validate.notNull(res.bodyStream); if (res.hasHeaderWithValue(CONTENT_ENCODING, "gzip")) { res.bodyStream = new GZIPInputStream(res.bodyStream); } else if (res.hasHeaderWithValue(CONTENT_ENCODING, "deflate")) { @@ -861,7 +880,7 @@ public Document parse() throws IOException { private void prepareByteData() { Validate.isTrue(executed, "Request must be executed (with .execute(), .get(), or .post() before getting response body"); - if (byteData == null) { + if (bodyStream != null && byteData == null) { Validate.isFalse(inputStreamRead, "Request has already been read (with .parse())"); try { byteData = DataUtil.readToByteBuffer(bodyStream, req.maxBodySize()); @@ -876,6 +895,7 @@ private void prepareByteData() { public String body() { prepareByteData(); + Validate.notNull(byteData); // charset gets set from header on execute, and from meta-equiv on parse. parse may not have happened yet String body = (charset == null ? DataUtil.UTF_8 : Charset.forName(charset)) .decode(byteData).toString(); @@ -885,6 +905,7 @@ public String body() { public byte[] bodyAsBytes() { prepareByteData(); + Validate.notNull(byteData); return byteData.array(); } @@ -904,10 +925,11 @@ public BufferedInputStream bodyStream() { // set up connection defaults, and details from request private static HttpURLConnection createConnection(Connection.Request req) throws IOException { + Proxy proxy = req.proxy(); final HttpURLConnection conn = (HttpURLConnection) ( - req.proxy() == null ? + proxy == null ? req.url().openConnection() : - req.url().openConnection(req.proxy()) + req.url().openConnection(proxy) ); conn.setRequestMethod(req.method().name()); @@ -950,8 +972,9 @@ private void safeClose() { } // set up url, method, header, cookies - private void setupFromConnection(HttpURLConnection conn, HttpConnection.Response previousResponse) throws IOException { + private Response(HttpURLConnection conn, Connection.Request request, @Nullable HttpConnection.Response previousResponse) throws IOException { this.conn = conn; + this.req = request; method = Method.valueOf(conn.getRequestMethod()); url = conn.getURL(); statusCode = conn.getResponseCode(); @@ -968,6 +991,11 @@ private void setupFromConnection(HttpURLConnection conn, HttpConnection.Response cookie(prevCookie.getKey(), prevCookie.getValue()); } previousResponse.safeClose(); + + // enforce too many redirects: + numRedirects = previousResponse.numRedirects + 1; + if (numRedirects >= MAX_REDIRECTS) + throw new IOException(String.format("Too many redirects occurred trying to load URL %s", previousResponse.url())); } } @@ -1022,14 +1050,14 @@ void processResponseHeaders(Map<String, List<String>> resHeaders) { } private @Nullable static String setOutputContentType(final Connection.Request req) { + final String contentType = req.header(CONTENT_TYPE); String bound = null; - if (req.hasHeader(CONTENT_TYPE)) { + if (contentType != null) { // no-op; don't add content type as already set (e.g. for requestBody()) // todo - if content type already set, we could add charset // if user has set content type to multipart/form-data, auto add boundary. - if(req.header(CONTENT_TYPE).contains(MULTIPART_FORM_DATA) && - !req.header(CONTENT_TYPE).contains("boundary")) { + if(contentType.contains(MULTIPART_FORM_DATA) && !contentType.contains("boundary")) { bound = DataUtil.mimeBoundary(); req.header(CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bound); } @@ -1044,20 +1072,21 @@ else if (needsMultipart(req)) { return bound; } - private static void writePost(final Connection.Request req, final OutputStream outputStream, @Nullable final String bound) throws IOException { + private static void writePost(final Connection.Request req, final OutputStream outputStream, @Nullable final String boundary) throws IOException { final Collection<Connection.KeyVal> data = req.data(); final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(outputStream, req.postDataCharset())); - if (bound != null) { + if (boundary != null) { // boundary will be set if we're in multipart mode for (Connection.KeyVal keyVal : data) { w.write("--"); - w.write(bound); + w.write(boundary); w.write("\r\n"); w.write("Content-Disposition: form-data; name=\""); w.write(encodeMimeName(keyVal.key())); // encodes " to %22 w.write("\""); - if (keyVal.hasInputStream()) { + final InputStream input = keyVal.inputStream(); + if (input != null) { w.write("; filename=\""); w.write(encodeMimeName(keyVal.value())); w.write("\"\r\nContent-Type: "); @@ -1065,7 +1094,7 @@ private static void writePost(final Connection.Request req, final OutputStream o w.write(contentType != null ? contentType : DefaultUploadType); w.write("\r\n\r\n"); w.flush(); // flush - DataUtil.crossStreams(keyVal.inputStream(), outputStream); + DataUtil.crossStreams(input, outputStream); outputStream.flush(); } else { w.write("\r\n\r\n"); @@ -1074,24 +1103,27 @@ private static void writePost(final Connection.Request req, final OutputStream o w.write("\r\n"); } w.write("--"); - w.write(bound); + w.write(boundary); w.write("--"); - } else if (req.requestBody() != null) { - // data will be in query string, we're sending a plaintext body - w.write(req.requestBody()); - } - else { - // regular form data (application/x-www-form-urlencoded) - boolean first = true; - for (Connection.KeyVal keyVal : data) { - if (!first) - w.append('&'); - else - first = false; - - w.write(URLEncoder.encode(keyVal.key(), req.postDataCharset())); - w.write('='); - w.write(URLEncoder.encode(keyVal.value(), req.postDataCharset())); + } else { + String body = req.requestBody(); + if (body != null) { + // data will be in query string, we're sending a plaintext body + w.write(body); + } + else { + // regular form data (application/x-www-form-urlencoded) + boolean first = true; + for (Connection.KeyVal keyVal : data) { + if (!first) + w.append('&'); + else + first = false; + + w.write(URLEncoder.encode(keyVal.key(), req.postDataCharset())); + w.write('='); + w.write(URLEncoder.encode(keyVal.value(), req.postDataCharset())); + } } } w.close(); @@ -1155,18 +1187,24 @@ private static boolean needsMultipart(Connection.Request req) { public static class KeyVal implements Connection.KeyVal { private String key; private String value; - private InputStream stream; - private String contentType; + private @Nullable InputStream stream; + private @Nullable String contentType; public static KeyVal create(String key, String value) { - return new KeyVal().key(key).value(value); + return new KeyVal(key, value); } public static KeyVal create(String key, String filename, InputStream stream) { - return new KeyVal().key(key).value(filename).inputStream(stream); + return new KeyVal(key, filename) + .inputStream(stream); } - private KeyVal() {} + private KeyVal(String key, String value) { + Validate.notEmpty(key, "Data key must not be empty"); + Validate.notNull(value, "Data value must not be null"); + this.key = key; + this.value = value; + } public KeyVal key(String key) { Validate.notEmpty(key, "Data key must not be empty"); diff --git a/src/main/java/org/jsoup/helper/Validate.java b/src/main/java/org/jsoup/helper/Validate.java index 01eb80a654..e934faa944 100644 --- a/src/main/java/org/jsoup/helper/Validate.java +++ b/src/main/java/org/jsoup/helper/Validate.java @@ -1,5 +1,7 @@ package org.jsoup.helper; +import javax.annotation.Nullable; + /** * Simple validation methods. Designed for jsoup internal use */ @@ -11,7 +13,7 @@ private Validate() {} * Validates that the object is not null * @param obj object to test */ - public static void notNull(Object obj) { + public static void notNull(@Nullable Object obj) { if (obj == null) throw new IllegalArgumentException("Object must not be null"); } @@ -21,7 +23,7 @@ public static void notNull(Object obj) { * @param obj object to test * @param msg message to output if validation fails */ - public static void notNull(Object obj, String msg) { + public static void notNull(@Nullable Object obj, String msg) { if (obj == null) throw new IllegalArgumentException(msg); } @@ -84,20 +86,20 @@ public static void noNullElements(Object[] objects, String msg) { } /** - * Validates that the string is not empty + * Validates that the string is not null and is not empty * @param string the string to test */ - public static void notEmpty(String string) { + public static void notEmpty(@Nullable String string) { if (string == null || string.length() == 0) throw new IllegalArgumentException("String must not be empty"); } /** - * Validates that the string is not empty + * Validates that the string is not null and is not empty * @param string the string to test * @param msg message to output if validation fails */ - public static void notEmpty(String string, String msg) { + public static void notEmpty(@Nullable String string, String msg) { if (string == null || string.length() == 0) throw new IllegalArgumentException(msg); } diff --git a/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java b/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java index f501ea3975..1aeac5178d 100644 --- a/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java +++ b/src/main/java/org/jsoup/internal/FieldsAreNonnullByDefault.java @@ -10,7 +10,7 @@ @Documented @Nonnull @TypeQualifierDefault(ElementType.FIELD) -@Retention(value = RetentionPolicy.RUNTIME) +@Retention(value = RetentionPolicy.CLASS) /** Indicates that fields types are not nullable, unless otherwise specified by @Nullable. diff --git a/src/main/java/org/jsoup/internal/NonnullByDefault.java b/src/main/java/org/jsoup/internal/NonnullByDefault.java index e3497045b8..7f6ed84d6c 100644 --- a/src/main/java/org/jsoup/internal/NonnullByDefault.java +++ b/src/main/java/org/jsoup/internal/NonnullByDefault.java @@ -10,7 +10,7 @@ @Documented @Nonnull @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) -@Retention(value = RetentionPolicy.RUNTIME) +@Retention(value = RetentionPolicy.CLASS) /** Indicates that all components (methods, returns, fields) are not nullable, unless otherwise specified by @Nullable. diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index fad0710b79..a18c7543a1 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -4,8 +4,6 @@ import org.jsoup.Jsoup; import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.integration.ParseTest; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -18,6 +16,21 @@ public class HttpConnectionTest { /* most actual network http connection tests are in integration */ + @Test public void canCreateEmptyConnection() { + HttpConnection con = new HttpConnection(); + assertEquals(Connection.Method.GET, con.request().method()); + assertThrows(IllegalArgumentException.class, () -> { + URL url = con.request().url(); + }); + } + + @Test public void throwsExceptionOnResponseWithoutExecute() { + assertThrows(IllegalArgumentException.class, () -> { + Connection con = HttpConnection.connect("http://example.com"); + con.response(); + }); + } + @Test public void throwsExceptionOnParseWithoutExecute() { assertThrows(IllegalArgumentException.class, () -> { Connection con = HttpConnection.connect("http://example.com"); @@ -248,7 +261,7 @@ public void caseInsensitiveHeaders(Locale locale) { con.execute(); } catch (IllegalArgumentException e) { threw = true; - assertEquals("URL must be specified to connect", e.getMessage()); + assertEquals("URL not yet set", e.getMessage()); } assertTrue(threw); } @@ -269,4 +282,15 @@ public void caseInsensitiveHeaders(Locale locale) { req.url(new URL(idn)); assertEquals(puny, req.url().toExternalForm()); } + + @Test public void validationErrorsOnExecute() throws IOException { + Connection con = new HttpConnection(); + boolean urlThrew = false; + try { + con.execute(); + } catch (IllegalArgumentException e) { + urlThrew = e.getMessage().contains("URL"); + } + assertTrue(urlThrew); + } } From b431fb2dd002c0425d5938ca2ce4d1c967336a5b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 20 Jan 2021 20:50:18 +1100 Subject: [PATCH 513/774] Adds easy request session-state and corrects cookie domain and path rules (#1476) --- pom.xml | 10 +- src/main/java/org/jsoup/Connection.java | 66 ++++++-- .../java/org/jsoup/HttpStatusException.java | 7 +- src/main/java/org/jsoup/Jsoup.java | 31 +++- .../java/org/jsoup/helper/CookieUtil.java | 90 +++++++++++ src/main/java/org/jsoup/helper/DataUtil.java | 5 +- .../java/org/jsoup/helper/HttpConnection.java | 141 +++++++++++++----- .../java/org/jsoup/internal/StringUtil.java | 66 +++++++- src/main/java/org/jsoup/nodes/Document.java | 36 ++++- .../java/org/jsoup/nodes/FormElement.java | 17 ++- .../org/jsoup/parser/HtmlTreeBuilder.java | 5 + .../java/org/jsoup/parser/ParseErrorList.java | 10 ++ .../java/org/jsoup/parser/ParseSettings.java | 4 + src/main/java/org/jsoup/parser/Parser.java | 14 ++ .../java/org/jsoup/parser/TreeBuilder.java | 6 + .../java/org/jsoup/parser/XmlTreeBuilder.java | 5 + .../org/jsoup/helper/HttpConnectionTest.java | 2 +- .../org/jsoup/integration/ConnectTest.java | 27 +++- .../java/org/jsoup/integration/SessionIT.java | 130 ++++++++++++++++ .../org/jsoup/integration/SessionTest.java | 140 +++++++++++++++++ .../org/jsoup/integration/TestServer.java | 2 +- .../integration/servlets/CookieServlet.java | 76 ++++++++++ .../integration/servlets/EchoServlet.java | 11 +- .../integration/servlets/FileServlet.java | 2 + .../jsoup/integration/servlets/SlowRider.java | 1 + .../java/org/jsoup/nodes/DocumentTest.java | 5 + .../java/org/jsoup/nodes/FormElementTest.java | 41 +++++ 27 files changed, 870 insertions(+), 80 deletions(-) create mode 100644 src/main/java/org/jsoup/helper/CookieUtil.java create mode 100644 src/test/java/org/jsoup/integration/SessionIT.java create mode 100644 src/test/java/org/jsoup/integration/SessionTest.java create mode 100644 src/test/java/org/jsoup/integration/servlets/CookieServlet.java diff --git a/pom.xml b/pom.xml index e1a2379538..bc1cfed96b 100644 --- a/pom.xml +++ b/pom.xml @@ -203,11 +203,19 @@ <onlyModified>false</onlyModified> <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> + <overrideCompatibilityChangeParameters> + <!-- 1.14.1 interface adds for cookies. rarely implemented outside of jsoup, and can't provide default impl in 7, so flag in changelog --> + <overrideCompatibilityChangeParameter> + <compatibilityChange>METHOD_ADDED_TO_INTERFACE</compatibilityChange> + <binaryCompatible>true</binaryCompatible> + <sourceCompatible>true</sourceCompatible> + </overrideCompatibilityChangeParameter> + </overrideCompatibilityChangeParameters> </parameter> </configuration> <executions> <execution> - <phase>verify</phase> + <phase>package</phase> <goals> <goal>cmp</goal> </goals> diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 841e0e2726..ef64faa431 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -8,6 +8,7 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.CookieStore; import java.net.Proxy; import java.net.URL; import java.util.Collection; @@ -15,16 +16,25 @@ import java.util.Map; /** - * The Connection interface is a convenient HTTP client to fetch content from the web, and parse them into Documents. - * <p> - * To get a new Connection, use {@link org.jsoup.Jsoup#connect(String)}. Connections contain {@link Connection.Request} - * and {@link Connection.Response} objects. The request objects are reusable as prototype requests. - * </p> - * <p> - * Request configuration can be made using either the shortcut methods in Connection (e.g. {@link #userAgent(String)}), - * or by methods in the Connection.Request object directly. All request configuration must be made before the request is - * executed. - * </p> + The Connection interface is a convenient HTTP client and session object to fetch content from the web, and parse them + into Documents. + <p>To start a new session, use either {@link org.jsoup.Jsoup#newSession()} or {@link org.jsoup.Jsoup#connect(String)}. + Connections contain {@link Connection.Request} and {@link Connection.Response} objects (once executed). Configuration + settings (URL, timeout, useragent, etc) set on a session will be applied by default to each subsequent request.</p> + <p>To start a new request from the session, use {@link #newRequest()}.</p> + <p>Cookies are stored in memory for the duration of the session. For that reason, do not use one single session for all + requests in a long-lived application, or you are likely to run out of memory, unless care is taken to clean up the + cookie store. The cookie store for the session is available via {@link #cookieStore()}. You may provide your own + implementation via {@link #cookieStore(java.net.CookieStore)} before making requests.</p> + <p>Request configuration can be made using either the shortcut methods in Connection (e.g. {@link #userAgent(String)}), + or by methods in the Connection.Request object directly. All request configuration must be made before the request is + executed. When used as an ongoing session, initialize all defaults prior to making multi-threaded {@link +#newRequest()}s.</p> + <p>Note that the term "Connection" used here does not mean that a long-lived connection is held against a server for + the lifetime of the Connection object. A socket connection is only made at the point of request execution ({@link +#execute()}, {@link #get()}, or {@link #post()}), and the server's response consumed.</p> + <p>For multi-threaded implementations, it is important to use a {@link #newRequest()} for each request. The session may + be shared across threads but a given request, not.</p> */ @SuppressWarnings("unused") public interface Connection { @@ -50,6 +60,13 @@ public final boolean hasBody() { } } + /** + Creates a new request, using this Connection as the session-state and to initialize the connection settings (which may then be independently on the returned Connection.Request object). + @return a new Connection object, with a shared Cookie Store and initialized settings from this Connection and Request + @since 1.14.1 + */ + Connection newRequest(); + /** * Set the request URL to fetch. The protocol must be HTTP or HTTPS. * @param url URL to connect to @@ -206,11 +223,15 @@ public final boolean hasBody() { Connection data(Map<String, String> data); /** - * Add a number of request data parameters. Multiple parameters may be set at once, e.g.: <code>.data("name", - * "jsoup", "language", "Java", "language", "English");</code> creates a query string like: - * <code>{@literal ?name=jsoup&language=Java&language=English}</code> - * @param keyvals a set of key value pairs. - * @return this Connection, for chaining + Add one or more request {@code key, val} data parameter pairs.<p>Multiple parameters may be set at once, e.g.: + <code>.data("name", "jsoup", "language", "Java", "language", "English");</code> creates a query string like: + <code>{@literal ?name=jsoup&language=Java&language=English}</code></p> + <p>For GET requests, data parameters will be sent on the request query string. For POST (and other methods that + contain a body), they will be sent as body form parameters, unless the body is explicitly set by {@link + #requestBody(String)}, in which case they will be query string parameters.</p> + + @param keyvals a set of key value pairs. + @return this Connection, for chaining */ Connection data(String... keyvals); @@ -265,6 +286,21 @@ public final boolean hasBody() { */ Connection cookies(Map<String, String> cookies); + /** + Provide a custom or pre-filled CookieStore to be used on requests made by this Connection. + @param cookieStore a cookie store to use for subsequent requests + @return this Connection, for chaining + @since 1.14.1 + */ + Connection cookieStore(CookieStore cookieStore); + + /** + Get the cookie store used by this Connection. + @return the cookie store + @since 1.14.1 + */ + CookieStore cookieStore(); + /** * Provide an alternate parser to use when parsing the response to a Document. If not set, defaults to the HTML * parser, unless the response content-type is XML, in which case the XML parser is used. diff --git a/src/main/java/org/jsoup/HttpStatusException.java b/src/main/java/org/jsoup/HttpStatusException.java index 2ad83b7d73..3e98aa63e3 100644 --- a/src/main/java/org/jsoup/HttpStatusException.java +++ b/src/main/java/org/jsoup/HttpStatusException.java @@ -10,7 +10,7 @@ public class HttpStatusException extends IOException { private final String url; public HttpStatusException(String message, int statusCode, String url) { - super(message); + super(message + ". Status=" + statusCode + ", URL=[" + url + "]"); this.statusCode = statusCode; this.url = url; } @@ -22,9 +22,4 @@ public int getStatusCode() { public String getUrl() { return url; } - - @Override - public String toString() { - return super.toString() + ". Status=" + statusCode + ", URL=" + url; - } } diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index ff74f7a3e2..3eb7f0b56c 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -62,7 +62,7 @@ public static Document parse(String html) { } /** - * Creates a new {@link Connection} to a URL. Use to fetch and parse a HTML page. + * Creates a new {@link Connection} (session), with the defined request URL. Use to fetch and parse a HTML page. * <p> * Use examples: * <ul> @@ -71,11 +71,40 @@ public static Document parse(String html) { * </ul> * @param url URL to connect to. The protocol must be {@code http} or {@code https}. * @return the connection. You can add data, cookies, and headers; set the user-agent, referrer, method; and then execute. + * @see #newSession() + * @see Connection#newRequest() */ public static Connection connect(String url) { return HttpConnection.connect(url); } + /** + Creates a new {@link Connection} to use as a session. Connection settings (user-agent, timeouts, URL, etc), and + cookies will be maintained for the session. Use examples: +<pre><code> +Connection session = Jsoup.newSession() + .timeout(20 * 1000) + .userAgent("FooBar 2000"); + +Document doc1 = session.newRequest() + .url("https://jsoup.org/").data("ref", "example") + .get(); +Document doc2 = session.newRequest() + .url("https://en.wikipedia.org/wiki/Main_Page") + .get(); +Connection con3 = session.newRequest(); +</code></pre> + + <p>For multi-threaded requests, it is safe to use this session between threads, but take care to call {@link + Connection#newRequest()} per request and not share that instance between threads when executing or parsing.</p> + + @return a connection + @since 1.14.1 + */ + public static Connection newSession() { + return new HttpConnection(); + } + /** Parse the contents of a file as HTML. diff --git a/src/main/java/org/jsoup/helper/CookieUtil.java b/src/main/java/org/jsoup/helper/CookieUtil.java new file mode 100644 index 0000000000..f375003753 --- /dev/null +++ b/src/main/java/org/jsoup/helper/CookieUtil.java @@ -0,0 +1,90 @@ +package org.jsoup.helper; + +import org.jsoup.Connection; +import org.jsoup.internal.StringUtil; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + Helper functions to support the Cookie Manager / Cookie Storage in HttpConnection. + + @since 1.14.1 */ +class CookieUtil { + // cookie manager get() wants request headers but doesn't use them, so we just pass a dummy object here + private static final Map<String, List<String>> EmptyRequestHeaders = Collections.unmodifiableMap(new HashMap<>()); + private static final String Sep = "; "; + private static final String CookieName = "Cookie"; + private static final String Cookie2Name = "Cookie2"; + + /** + Pre-request, get any applicable headers out of the Request cookies and the Cookie Store, and add them to the request + headers. If the Cookie Store duplicates any Request cookies (same name and value), they will be discarded. + */ + static void applyCookiesToRequest(HttpConnection.Request req, HttpURLConnection con) throws IOException { + // Request key/val cookies. LinkedHashSet used to preserve order, as cookie store will return most specific path first + Set<String> cookieSet = requestCookieSet(req); + Set<String> cookies2 = null; + + // stored: + Map<String, List<String>> storedCookies = req.cookieManager().get(asUri(req.url), EmptyRequestHeaders); + for (Map.Entry<String, List<String>> entry : storedCookies.entrySet()) { + // might be Cookie: name=value; name=value\nCookie2: name=value; name=value + List<String> cookies = entry.getValue(); // these will be name=val + if (cookies == null || cookies.size() == 0) // the cookie store often returns just an empty "Cookie" key, no val + continue; + + String key = entry.getKey(); // Cookie or Cookie2 + Set<String> set; + if (CookieName.equals(key)) + set = cookieSet; + else if (Cookie2Name.equals(key)) { + set = new HashSet<>(); + cookies2 = set; + } else { + continue; // unexpected header key + } + set.addAll(cookies); + } + + if (cookieSet.size() > 0) + con.addRequestProperty(CookieName, StringUtil.join(cookieSet, Sep)); + if (cookies2 != null && cookies2.size() > 0) + con.addRequestProperty(Cookie2Name, StringUtil.join(cookies2, Sep)); + } + + private static LinkedHashSet<String> requestCookieSet(Connection.Request req) { + LinkedHashSet<String> set = new LinkedHashSet<>(); + // req cookies are the wildcard key/val cookies (no domain, path, etc) + for (Map.Entry<String, String> cookie : req.cookies().entrySet()) { + set.add(cookie.getKey() + "=" + cookie.getValue()); + } + return set; + } + + static URI asUri(URL url) throws IOException { + try { + return url.toURI(); + } catch (URISyntaxException e) { // this would be a WTF because we construct the URL + MalformedURLException ue = new MalformedURLException(e.getMessage()); + ue.initCause(e); + throw ue; + } + } + + static void storeCookies(HttpConnection.Request req, URL url, Map<String, List<String>> resHeaders) throws IOException { + req.cookieManager().put(CookieUtil.asUri(url), resHeaders); // stores cookies for session + + } +} diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 7806bd0efe..406ea5bdfc 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -36,6 +36,7 @@ * Internal static utilities for handling data. * */ +@SuppressWarnings("CharsetObjectCanBeUsed") public final class DataUtil { private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:[\"'])?([^\\s,;\"']*)"); public static final Charset UTF_8 = Charset.forName("UTF-8"); // Don't use StandardCharsets, as those only appear in Android API 19, and we target 10. @@ -59,7 +60,7 @@ private DataUtil() {} * @return Document * @throws IOException on IO error */ - public static Document load(File in, String charsetName, String baseUri) throws IOException { + public static Document load(File in, @Nullable String charsetName, String baseUri) throws IOException { InputStream stream = new FileInputStream(in); String name = Normalizer.lowerCase(in.getName()); if (name.endsWith(".gz") || name.endsWith(".z")) { @@ -264,7 +265,7 @@ static String mimeBoundary() { } private static @Nullable BomCharset detectCharsetFromBom(final ByteBuffer byteData) { - final Buffer buffer = byteData; // .mark and rewind used to return Buffer, now ByteBuffer, so cast for backward compat + @SuppressWarnings("UnnecessaryLocalVariable") final Buffer buffer = byteData; // .mark and rewind used to return Buffer, now ByteBuffer, so cast for backward compat buffer.mark(); byte[] bom = new byte[4]; if (byteData.remaining() >= bom.length) { diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 41565c6cc6..7914803ada 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -20,6 +20,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.net.CookieManager; +import java.net.CookieStore; import java.net.HttpURLConnection; import java.net.IDN; import java.net.InetSocketAddress; @@ -69,12 +71,22 @@ public class HttpConnection implements Connection { private static final Charset UTF_8 = Charset.forName("UTF-8"); // Don't use StandardCharsets, not in Android API 10. private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + /** + Create a new Connection, with the request URL specified. + @param url the URL to fetch from + @return a new Connection object + */ public static Connection connect(String url) { Connection con = new HttpConnection(); con.url(url); return con; } + /** + Create a new Connection, with the request URL specified. + @param url the URL to fetch from + @return a new Connection object + */ public static Connection connect(URL url) { Connection con = new HttpConnection(); con.url(url); @@ -88,6 +100,14 @@ public HttpConnection() { req = new Request(); } + /** + Create a new Request by deep-copying an existing Request + @param copy the request to copy + */ + HttpConnection(Request copy) { + req = new Request(copy); + } + /** * Encodes the input URL into a safe ASCII URL string * @param url unescaped URL @@ -138,9 +158,21 @@ private static String encodeMimeName(String val) { return val.replace("\"", "%22"); } - private Connection.Request req; + private HttpConnection.Request req; private @Nullable Connection.Response res; + @Override + public Connection newRequest() { + // copy the prototype request for the different settings, cookie manager, etc + return new HttpConnection(req); + } + + /** Create a new Connection that just wraps the provided Request and Response */ + private HttpConnection(Request req, Response res) { + this.req = req; + this.res = res; + } + public Connection url(URL url) { req.url(url); return this; @@ -299,6 +331,18 @@ public Connection cookies(Map<String, String> cookies) { return this; } + @Override + public Connection cookieStore(CookieStore cookieStore) { + // create a new cookie manager using the new store + req.cookieManager = new CookieManager(cookieStore, null); + return this; + } + + @Override + public CookieStore cookieStore() { + return req.cookieManager.getCookieStore(); + } + public Connection parser(Parser parser) { req.parser(parser); return this; @@ -328,7 +372,7 @@ public Connection.Request request() { } public Connection request(Connection.Request request) { - req = request; + req = (HttpConnection.Request) request; // will throw a class-cast exception if the user has extended some but not all of Connection; that's desired return this; } @@ -371,9 +415,19 @@ private Base() { cookies = new LinkedHashMap<>(); } + private Base(Base<T> copy) { + url = copy.url; // unmodifiable object + method = copy.method; + headers = new LinkedHashMap<>(); + for (Map.Entry<String, List<String>> entry : copy.headers.entrySet()) { + headers.put(entry.getKey(), new ArrayList<>(entry.getValue())); + } + cookies = new LinkedHashMap<>(); cookies.putAll(copy.cookies); // just holds strings + } + public URL url() { if (url == UnsetUrl) - throw new IllegalArgumentException("URL not yet set"); + throw new IllegalArgumentException("URL not set. Make sure to call #url(...) before executing the request."); return url; } @@ -576,6 +630,7 @@ public static class Request extends HttpConnection.Base<Connection.Request> impl System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); // make sure that we can send Sec-Fetch-Site headers etc. } + private @Nullable Proxy proxy; private int timeoutMilliseconds; private int maxBodySizeBytes; @@ -588,8 +643,11 @@ public static class Request extends HttpConnection.Base<Connection.Request> impl private boolean parserDefined = false; // called parser(...) vs initialized in ctor private String postDataCharset = DataUtil.defaultCharsetName; private @Nullable SSLSocketFactory sslSocketFactory; + private CookieManager cookieManager; + private volatile boolean executing = false; Request() { + super(); timeoutMilliseconds = 30000; // 30 seconds maxBodySizeBytes = 1024 * 1024 * 2; // 2MB followRedirects = true; @@ -598,6 +656,25 @@ public static class Request extends HttpConnection.Base<Connection.Request> impl addHeader("Accept-Encoding", "gzip"); addHeader(USER_AGENT, DEFAULT_UA); parser = Parser.htmlParser(); + cookieManager = new CookieManager(); // creates a default InMemoryCookieStore + } + + Request(Request copy) { + super(copy); + proxy = copy.proxy; + postDataCharset = copy.postDataCharset; + timeoutMilliseconds = copy.timeoutMilliseconds; + maxBodySizeBytes = copy.maxBodySizeBytes; + followRedirects = copy.followRedirects; + data = new ArrayList<>(); data.addAll(copy.data()); // this is shallow, but holds immutable string keyval, and possibly an InputStream which can only be read once anyway, so using as a prototype would be unsupported + body = copy.body; + ignoreHttpErrors = copy.ignoreHttpErrors; + ignoreContentType = copy.ignoreContentType; + parser = copy.parser.newInstance(); // parsers and their tree-builders maintain state, so need a fresh copy + parserDefined = copy.parserDefined; + sslSocketFactory = copy.sslSocketFactory; // these are all synchronized so safe to share + cookieManager = copy.cookieManager; + executing = false; } public Proxy proxy() { @@ -708,6 +785,10 @@ public Connection.Request postDataCharset(String charset) { public String postDataCharset() { return postDataCharset; } + + CookieManager cookieManager() { + return cookieManager; + } } public static class Response extends HttpConnection.Base<Connection.Response> implements Connection.Response { @@ -723,7 +804,7 @@ public static class Response extends HttpConnection.Base<Connection.Response> im private boolean executed = false; private boolean inputStreamRead = false; private int numRedirects = 0; - private final Connection.Request req; + private final HttpConnection.Request req; /* * Matches XML content types (like text/xml, application/xhtml+xml;charset=UTF8, etc) @@ -742,11 +823,15 @@ public static class Response extends HttpConnection.Base<Connection.Response> im contentType = null; } - static Response execute(Connection.Request req) throws IOException { + static Response execute(HttpConnection.Request req) throws IOException { return execute(req, null); } - static Response execute(Connection.Request req, @Nullable Response previousResponse) throws IOException { + static Response execute(HttpConnection.Request req, @Nullable Response previousResponse) throws IOException { + synchronized (req) { + Validate.isFalse(req.executing, "Multiple threads were detected trying to execute the same request concurrently. Make sure to use Connection#newRequest() and do not share an executing request between threads."); + req.executing = true; + } Validate.notNull(req, "Request must not be null"); URL url = req.url(); Validate.notNull(url, "URL must be specified to connect"); @@ -792,9 +877,7 @@ else if (methodHasBody) URL redir = StringUtil.resolve(req.url(), location); req.url(encodeUrl(redir)); - for (Map.Entry<String, String> cookie : res.cookies.entrySet()) { // add response cookies to request (for e.g. login posts) - req.cookie(cookie.getKey(), cookie.getValue()); - } + req.executing = false; return execute(req, res); } if ((status < 200 || status >= 400) && !req.ignoreHttpErrors()) @@ -812,10 +895,7 @@ else if (methodHasBody) // switch to the XML parser if content type is xml and not parser not explicitly set if (contentType != null && xmlContentTypeRxp.matcher(contentType).matches()) { - // only flip it if a HttpConnection.Request (i.e. don't presume other impls want it): - if (req instanceof HttpConnection.Request && !((Request) req).parserDefined) { - req.parser(Parser.xmlParser()); - } + if (!req.parserDefined) req.parser(Parser.xmlParser()); } res.charset = DataUtil.getCharsetFromContentType(res.contentType); // may be null, readInputStream deals with it @@ -837,6 +917,8 @@ else if (methodHasBody) } catch (IOException e) { if (res != null) res.safeClose(); // will be non-null if got to conn throw e; + } finally { + req.executing = false; } res.executed = true; @@ -872,6 +954,7 @@ public Document parse() throws IOException { } Validate.isFalse(inputStreamRead, "Input stream already read and parsed, cannot re-read."); Document doc = DataUtil.parseInputStream(bodyStream, charset, url.toExternalForm(), req.parser()); + doc.connection(new HttpConnection(req, this)); // because we're static, don't have the connection obj. // todo - maybe hold in the req? charset = doc.outputSettings().charset().name(); // update charset from meta-equiv, possibly inputStreamRead = true; safeClose(); @@ -924,7 +1007,7 @@ public BufferedInputStream bodyStream() { } // set up connection defaults, and details from request - private static HttpURLConnection createConnection(Connection.Request req) throws IOException { + private static HttpURLConnection createConnection(HttpConnection.Request req) throws IOException { Proxy proxy = req.proxy(); final HttpURLConnection conn = (HttpURLConnection) ( proxy == null ? @@ -941,8 +1024,7 @@ private static HttpURLConnection createConnection(Connection.Request req) throws ((HttpsURLConnection) conn).setSSLSocketFactory(req.sslSocketFactory()); if (req.method().hasBody()) conn.setDoOutput(true); - if (req.cookies().size() > 0) - conn.addRequestProperty("Cookie", getRequestCookieString(req)); + CookieUtil.applyCookiesToRequest(req, conn); // from the Request key/val cookies and the Cookie Store for (Map.Entry<String, List<String>> header : req.multiHeaders().entrySet()) { for (String value : header.getValue()) { conn.addRequestProperty(header.getKey(), value); @@ -972,7 +1054,7 @@ private void safeClose() { } // set up url, method, header, cookies - private Response(HttpURLConnection conn, Connection.Request request, @Nullable HttpConnection.Response previousResponse) throws IOException { + private Response(HttpURLConnection conn, HttpConnection.Request request, @Nullable HttpConnection.Response previousResponse) throws IOException { this.conn = conn; this.req = request; method = Method.valueOf(conn.getRequestMethod()); @@ -982,10 +1064,11 @@ private Response(HttpURLConnection conn, Connection.Request request, @Nullable H contentType = conn.getContentType(); Map<String, List<String>> resHeaders = createHeaderMap(conn); - processResponseHeaders(resHeaders); + processResponseHeaders(resHeaders); // includes cookie key/val read during header scan + CookieUtil.storeCookies(req, url, resHeaders); // add set cookies to cookie store - // if from a redirect, map previous response cookies into this response - if (previousResponse != null) { + if (previousResponse != null) { // was redirected + // map previous response cookies into this response cookies() object for (Map.Entry<String, String> prevCookie : previousResponse.cookies().entrySet()) { if (!hasCookie(prevCookie.getKey())) cookie(prevCookie.getKey(), prevCookie.getValue()); @@ -1037,9 +1120,9 @@ void processResponseHeaders(Map<String, List<String>> resHeaders) { TokenQueue cd = new TokenQueue(value); String cookieName = cd.chompTo("=").trim(); String cookieVal = cd.consumeTo(";").trim(); - // ignores path, date, domain, validateTLSCertificates et al. req'd? + // ignores path, date, domain, validateTLSCertificates et al. full details will be available in cookiestore if required // name not blank, value not null - if (cookieName.length() > 0) + if (cookieName.length() > 0 && !cookies.containsKey(cookieName)) // if duplicates, only keep the first cookie(cookieName, cookieVal); } } @@ -1129,20 +1212,6 @@ private static void writePost(final Connection.Request req, final OutputStream o w.close(); } - private static String getRequestCookieString(Connection.Request req) { - StringBuilder sb = StringUtil.borrowBuilder(); - boolean first = true; - for (Map.Entry<String, String> cookie : req.cookies().entrySet()) { - if (!first) - sb.append("; "); - else - first = false; - sb.append(cookie.getKey()).append('=').append(cookie.getValue()); - // todo: spec says only ascii, no escaping / encoding defined. validate on set? or escape somehow here? - } - return StringUtil.releaseBuilder(sb); - } - // for get url reqs, serialise the data map into the url private static void serialiseRequestUrl(Connection.Request req) throws IOException { URL in = req.url(); diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index c6b8405f62..f23c872358 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -2,6 +2,7 @@ import org.jsoup.helper.Validate; +import javax.annotation.Nullable; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; @@ -10,7 +11,8 @@ import java.util.Stack; /** - * A minimal String utility class. Designed for internal jsoup use only. + A minimal String utility class. Designed for <b>internal</b> jsoup use only - the API and outcome may change without + notice. */ public final class StringUtil { // memoised padding up to 21 @@ -25,7 +27,7 @@ public final class StringUtil { * @param sep string to place between strings * @return joined string */ - public static String join(Collection strings, String sep) { + public static String join(Collection<?> strings, String sep) { return join(strings.iterator(), sep); } @@ -35,7 +37,7 @@ public static String join(Collection strings, String sep) { * @param sep string to place between strings * @return joined string */ - public static String join(Iterator strings, String sep) { + public static String join(Iterator<?> strings, String sep) { if (!strings.hasNext()) return ""; @@ -43,12 +45,12 @@ public static String join(Iterator strings, String sep) { if (!strings.hasNext()) // only one, avoid builder return start; - StringBuilder sb = StringUtil.borrowBuilder().append(start); + StringJoiner j = new StringJoiner(sep); + j.add(start); while (strings.hasNext()) { - sb.append(sep); - sb.append(strings.next()); + j.add(strings.next()); } - return StringUtil.releaseBuilder(sb); + return j.complete(); } /** @@ -61,6 +63,56 @@ public static String join(String[] strings, String sep) { return join(Arrays.asList(strings), sep); } + /** + A StringJoiner allows incremental / filtered joining of a set of stringable objects. + @since 1.14.1 + */ + public static class StringJoiner { + @Nullable StringBuilder sb = borrowBuilder(); // sets null on builder release so can't accidentally be reused + final String separator; + boolean first = true; + + /** + Create a new joiner, that uses the specified separator. MUST call {@link #complete()} or will leak a thread + local string builder. + + @param separator the token to insert between strings + */ + public StringJoiner(String separator) { + this.separator = separator; + } + + /** + Add another item to the joiner, will be separated + */ + public StringJoiner add(Object stringy) { + Validate.notNull(sb); // don't reuse + if (!first) + sb.append(separator); + sb.append(stringy); + first = false; + return this; + } + + /** + Append content to the current item; not separated + */ + public StringJoiner append(Object stringy) { + Validate.notNull(sb); // don't reuse + sb.append(stringy); + return this; + } + + /** + Return the joined string, and release the builder back to the pool. This joiner cannot be reused. + */ + public String complete() { + String string = releaseBuilder(sb); + sb = null; + return string; + } + } + /** * Returns space padding (up to a max of 30). * @param width amount of padding desired diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index c9d2bf20a1..1e1217bba7 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -1,5 +1,7 @@ package org.jsoup.nodes; +import org.jsoup.Connection; +import org.jsoup.Jsoup; import org.jsoup.helper.DataUtil; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; @@ -20,10 +22,11 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class Document extends Element { + private @Nullable Connection connection; // the connection this doc was fetched from, if any private OutputSettings outputSettings = new OutputSettings(); private Parser parser; // the parser used to parse this document private QuirksMode quirksMode = QuirksMode.noQuirks; - private String location; + private final String location; private boolean updateMetaCharset = false; /** @@ -58,10 +61,24 @@ public static Document createShell(String baseUri) { /** * Get the URL this Document was parsed from. If the starting URL is a redirect, * this will return the final URL from which the document was served from. + * <p>Will return an empty string if the location is unknown (e.g. if parsed from a String). * @return location */ public String location() { - return location; + return location; + } + + /** + Returns the Connection (Request/Response) object that was used to fetch this document, if any; otherwise, a new + default Connection object. This can be used to continue a session, preserving settings and cookies, etc. + @return the Connection (session) associated with this Document, or an empty one otherwise. + @see Connection#newRequest() + */ + public Connection connection() { + if (connection == null) + return Jsoup.newSession(); + else + return connection; } /** @@ -608,4 +625,19 @@ public Document parser(Parser parser) { this.parser = parser; return this; } + + /** + Set the Connection used to fetch this document. This Connection is used as a session object when further requests are + made (e.g. when a form is submitted). + + @param connection to set + @return this document, for chaining + @see Connection#newRequest() + @since 1.14.1 + */ + public Document connection(Connection connection) { + Validate.notNull(connection); + this.connection = connection; + return this; + } } diff --git a/src/main/java/org/jsoup/nodes/FormElement.java b/src/main/java/org/jsoup/nodes/FormElement.java index 261fc1e0a0..e3404af50b 100644 --- a/src/main/java/org/jsoup/nodes/FormElement.java +++ b/src/main/java/org/jsoup/nodes/FormElement.java @@ -53,11 +53,14 @@ protected void removeChild(Node out) { } /** - * Prepare to submit this form. A Connection object is created with the request set up from the form values. You - * can then set up other options (like user-agent, timeout, cookies), then execute it. - * @return a connection prepared from the values of this form. - * @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the - * document's base URI when parsing. + Prepare to submit this form. A Connection object is created with the request set up from the form values. This + Connection will inherit the settings and the cookies (etc) of the connection/session used to request this Document + (if any), as available in {@link Document#connection()} + <p>You can then set up other options (like user-agent, timeout, cookies), then execute it.</p> + + @return a connection prepared from the values of this form, in the same session as the one used to request it + @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the + document's base URI when parsing. */ public Connection submit() { String action = hasAttr("action") ? absUrl("action") : baseUri(); @@ -65,7 +68,9 @@ public Connection submit() { Connection.Method method = attr("method").equalsIgnoreCase("POST") ? Connection.Method.POST : Connection.Method.GET; - return Jsoup.connect(action) + Document owner = ownerDocument(); + Connection connection = owner != null? owner.connection().newRequest() : Jsoup.newSession(); + return connection.url(action) .data(formData()) .method(method); } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 3009d08cdb..6d8c7142fb 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -62,6 +62,11 @@ ParseSettings defaultSettings() { return ParseSettings.htmlDefault; } + @Override + HtmlTreeBuilder newInstance() { + return new HtmlTreeBuilder(); + } + @Override @ParametersAreNonnullByDefault protected void initialiseParse(Reader input, String baseUri, Parser parser) { super.initialiseParse(input, baseUri, parser); diff --git a/src/main/java/org/jsoup/parser/ParseErrorList.java b/src/main/java/org/jsoup/parser/ParseErrorList.java index b2ece0c5d6..a1c6227716 100644 --- a/src/main/java/org/jsoup/parser/ParseErrorList.java +++ b/src/main/java/org/jsoup/parser/ParseErrorList.java @@ -9,12 +9,22 @@ */ public class ParseErrorList extends ArrayList<ParseError>{ private static final int INITIAL_CAPACITY = 16; + private final int initialCapacity; private final int maxSize; ParseErrorList(int initialCapacity, int maxSize) { super(initialCapacity); + this.initialCapacity = initialCapacity; this.maxSize = maxSize; } + + /** + Create a new ParseErrorList with the same settings, but no errors in the list + @param copy initial and max size details to copy + */ + ParseErrorList(ParseErrorList copy) { + this(copy.initialCapacity, copy.maxSize); + } boolean canAddError() { return size() < maxSize; diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index 39485415f5..f94eecff1a 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -49,6 +49,10 @@ public ParseSettings(boolean tag, boolean attribute) { preserveAttributeCase = attribute; } + ParseSettings(ParseSettings copy) { + this(copy.preserveTagCase, copy.preserveAttributeCase); + } + /** * Normalizes a tag name according to the case preservation setting. */ diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index 2c75538474..8ff9667e44 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -26,6 +26,20 @@ public Parser(TreeBuilder treeBuilder) { settings = treeBuilder.defaultSettings(); errors = ParseErrorList.noTracking(); } + + /** + Creates a new Parser as a deep copy of this; including initializing a new TreeBuilder. Allows independent (multi-threaded) use. + @return a copied parser + */ + public Parser newInstance() { + return new Parser(this); + } + + private Parser(Parser copy) { + treeBuilder = copy.treeBuilder.newInstance(); // because extended + errors = new ParseErrorList(copy.errors); // only copies size, not contents + settings = new ParseSettings(copy.settings); + } public Document parseInput(String html, String baseUri) { return treeBuilder.parse(new StringReader(html), baseUri, this); diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 197ce3b3bf..1f37f3345d 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -59,6 +59,12 @@ Document parse(Reader input, String baseUri, Parser parser) { return doc; } + /** + Create a new copy of this TreeBuilder + @return copy, ready for a new parse + */ + abstract TreeBuilder newInstance(); + abstract List<Node> parseFragment(String inputFragment, Element context, String baseUri, Parser parser); protected void runParser() { diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index b85cc3f585..0b6d02d9d4 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -44,6 +44,11 @@ Document parse(String input, String baseUri) { return parse(new StringReader(input), baseUri, new Parser(this)); } + @Override + XmlTreeBuilder newInstance() { + return new XmlTreeBuilder(); + } + @Override protected boolean process(Token token) { // start tag, end tag, doctype, comment, character, eof diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index a18c7543a1..3ed9cf9f6c 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -261,7 +261,7 @@ public void caseInsensitiveHeaders(Locale locale) { con.execute(); } catch (IllegalArgumentException e) { threw = true; - assertEquals("URL not yet set", e.getMessage()); + assertEquals("URL not set. Make sure to call #url(...) before executing the request.", e.getMessage()); } assertTrue(threw); } diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index f11eef5524..28e79dac60 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -96,7 +96,7 @@ public void throwsExceptionOn404() { Document doc = con.get(); } catch (HttpStatusException e) { threw = true; - assertEquals("org.jsoup.HttpStatusException: HTTP error fetching URL. Status=404, URL=" + e.getUrl(), e.toString()); + assertEquals("org.jsoup.HttpStatusException: HTTP error fetching URL. Status=404, URL=[" + e.getUrl() + "]", e.toString()); assertTrue(e.getUrl().startsWith(url)); assertEquals(404, e.getStatusCode()); } catch (IOException e) { @@ -363,6 +363,22 @@ public void multiCookieSet() throws IOException { assertEquals("token=asdfg123; uid=jhy", ihVal("Cookie", doc)); } + @Test public void requestCookiesSurviveRedirect() throws IOException { + // this test makes sure that Request keyval cookies (not in the cookie store) are sent on subsequent redirections, + // when not using the session method + Connection con = Jsoup.connect(RedirectServlet.Url) + .data(RedirectServlet.LocationParam, echoUrl) + .cookie("LetMeIn", "True") + .cookie("DoesItWork", "Yes"); + + Connection.Response res = con.execute(); + assertEquals(0, res.cookies().size()); // were not set by Redir or Echo servlet + Document doc = res.parse(); + assertEquals(echoUrl, doc.location()); + assertEquals("True", ihVal("Cookie: LetMeIn", doc)); + assertEquals("Yes", ihVal("Cookie: DoesItWork", doc)); + } + @Test public void supportsDeflate() throws IOException { Connection.Response res = Jsoup.connect(Deflateservlet.Url).execute(); @@ -646,4 +662,13 @@ public void maxBodySize() throws IOException { assertEquals(actualDocText, largeRes.parse().text().length()); assertEquals(actualDocText, unlimitedRes.parse().text().length()); } + + @Test public void repeatable() throws IOException { + String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K + Connection con = Jsoup.connect(url).parser(Parser.xmlParser()); + Document doc1 = con.get(); + Document doc2 = con.get(); + assertEquals("Large HTML", doc1.title()); + assertEquals("Large HTML", doc2.title()); + } } diff --git a/src/test/java/org/jsoup/integration/SessionIT.java b/src/test/java/org/jsoup/integration/SessionIT.java new file mode 100644 index 0000000000..29fff08013 --- /dev/null +++ b/src/test/java/org/jsoup/integration/SessionIT.java @@ -0,0 +1,130 @@ +package org.jsoup.integration; + +import org.jsoup.Connection; +import org.jsoup.Jsoup; +import org.jsoup.UncheckedIOException; +import org.jsoup.integration.servlets.EchoServlet; +import org.jsoup.integration.servlets.FileServlet; +import org.jsoup.integration.servlets.SlowRider; +import org.jsoup.nodes.Document; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** Integration tests to test longer running Connection */ +public class SessionIT { + @BeforeAll + public static void setUp() { + TestServer.start(); + } + + @AfterAll + public static void tearDown() { + TestServer.stop(); + } + + @Test + public void multiThread() throws InterruptedException { + int numThreads = 20; + int numThreadLoops = 5; + String[] urls = { + FileServlet.urlTo("/htmltests/smh-biz-article-1.html.gz"), + FileServlet.urlTo("/htmltests/news-com-au-home.html.gz"), + FileServlet.urlTo("/htmltests/google-ipod.html.gz"), + FileServlet.urlTo("/htmltests/large.html"), + }; + String[] titles = { + "The board’s next fear: the female quota", + "News.com.au | News from Australia and around the world online | NewsComAu", + "ipod - Google Search", + "Large HTML" + }; + ThreadCatcher catcher = new ThreadCatcher(); + + Connection session = Jsoup.newSession(); + + Thread[] threads = new Thread[numThreads]; + for (int threadNum = 0; threadNum < numThreads; threadNum++) { + Thread thread = new Thread(() -> { + for (int loop = 0; loop < numThreadLoops; loop++) { + for (int i = 0; i < urls.length; i++) { + try { + Document doc = session.newRequest().url(urls[i]).get(); + assertEquals(titles[i], doc.title()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + }); + thread.setName("Runner-" + threadNum); + thread.start(); + thread.setUncaughtExceptionHandler(catcher); + threads[threadNum] = thread; + } + + // now join them all + for (Thread thread : threads) { + thread.join(); + } + + assertEquals(0, catcher.exceptionCount.get()); + } + + // test that we throw a nice clear exception if you try to multi-thread by forget .newRequest() + @Test + public void multiThreadWithoutNewRequestBlowsUp() throws InterruptedException { + int numThreads = 20; + String url = SlowRider.Url + "?" + SlowRider.MaxTimeParam + "=10000"; // this makes sure that the first req is still executing whilst the others run + String title = "Slow Rider"; + + ThreadCatcher catcher = new ThreadCatcher(); + Connection session = Jsoup.newSession(); + + Thread[] threads = new Thread[numThreads]; + for (int threadNum = 0; threadNum < numThreads; threadNum++) { + Thread thread = new Thread(() -> { + try { + Document doc = session.url(url).get(); + assertEquals(title, doc.title()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + thread.setName("Runner-" + threadNum); + thread.start(); + thread.setUncaughtExceptionHandler(catcher); + threads[threadNum] = thread; + } + + // now join them all + for (Thread thread : threads) { + thread.join(); + } + + // only one should have passed, rest should have blown up (assuming the started whilst other was running) + assertEquals(numThreads - 1, catcher.multiThreadExceptions.get()); + assertEquals(numThreads - 1, catcher.exceptionCount.get()); + } + + + static class ThreadCatcher implements Thread.UncaughtExceptionHandler { + AtomicInteger exceptionCount = new AtomicInteger(); + AtomicInteger multiThreadExceptions = new AtomicInteger(); + + @Override + public void uncaughtException(Thread t, Throwable e) { + if (e instanceof IllegalArgumentException && e.getMessage().contains("Multiple threads")) + multiThreadExceptions.incrementAndGet(); + else + e.printStackTrace(); + exceptionCount.incrementAndGet(); + } + } + +} diff --git a/src/test/java/org/jsoup/integration/SessionTest.java b/src/test/java/org/jsoup/integration/SessionTest.java new file mode 100644 index 0000000000..1de2e0cead --- /dev/null +++ b/src/test/java/org/jsoup/integration/SessionTest.java @@ -0,0 +1,140 @@ +package org.jsoup.integration; + +import org.jsoup.Connection; +import org.jsoup.Jsoup; +import org.jsoup.integration.servlets.CookieServlet; +import org.jsoup.integration.servlets.EchoServlet; +import org.jsoup.integration.servlets.FileServlet; +import org.jsoup.nodes.Document; +import org.jsoup.parser.Parser; +import org.jsoup.select.Elements; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SessionTest { + @BeforeAll + public static void setUp() { + TestServer.start(); + } + + @AfterAll + public static void tearDown() { + TestServer.stop(); + } + + private static Elements keyEls(String key, Document doc) { + return doc.select("th:contains(" + key + ") + td"); + } + + private static String keyText(String key, Document doc) { + return doc.selectFirst("th:contains(" + key + ") + td").text(); + } + + @Test + public void testPathScopedCookies() throws IOException { + final Connection session = Jsoup.newSession(); + final String userAgent = "Jsoup Testalot v0.1"; + + session.userAgent(userAgent); + session.url(CookieServlet.Url); + + // should have no cookies: + Connection con1 = session.newRequest(); + Document doc1 = con1.get(); + assertEquals(0, doc1.select("table tr").size()); // none sent to servlet + + // set the cookies + Connection con2 = session.newRequest().data(CookieServlet.SetCookiesParam, "1"); + Document doc2 = con2.get(); + assertEquals(0, doc2.select("table tr").size()); // none sent to servlet - we just got them! + Map<String, String> cookies = con2.response().cookies(); // simple cookie response, all named "One", so should be first sent + assertEquals(1, cookies.size()); + assertEquals("Root", cookies.get("One")); + + // todo - interrogate cookie-store + + // check that they are sent and filtered to the right path + Connection con3 = session.newRequest(); + Document doc3 = con3.get(); + assertCookieServlet(doc3); + + Document echo = session.newRequest().url(EchoServlet.Url).get(); + assertEchoServlet(echo); + assertEquals(userAgent, keyText("User-Agent", echo)); // check that customer user agent sent on session arrived + + // check that cookies aren't set out of the session + Document doc4 = Jsoup.newSession().url(CookieServlet.Url).get(); + assertEquals(0, doc4.select("table tr").size()); // none sent to servlet + + // check can add local ones also + Document doc5 = session.newRequest().cookie("Bar", "Qux").get(); + Elements doc5Bar = keyEls("Bar", doc5); + assertEquals("Qux", doc5Bar.first().text()); + } + + // validate that only cookies set by cookie servlet get to the cookie servlet path + private void assertCookieServlet(Document doc) { + assertEquals(2, doc.select("table tr").size()); // two of three sent to servlet (/ and /CookieServlet) + Elements doc3Els = keyEls("One", doc); + assertEquals(2, doc3Els.size()); + assertEquals("CookieServlet", doc3Els.get(0).text()); // ordered by most specific path + assertEquals("Root", doc3Els.get(1).text()); // ordered by most specific path + } + + // validate that only for echo servlet + private void assertEchoServlet(Document doc) { + Elements echoEls = keyEls("Cookie: One", doc); // two of three sent to servlet (/ and /EchoServlet) + assertEquals(2, echoEls.size()); + assertEquals("EchoServlet", echoEls.get(0).text()); // ordered by most specific path - /Echo + assertEquals("Root", echoEls.get(1).text()); // ordered by most specific path - / + } + + @Test + public void testPathScopedCookiesOnRedirect() throws IOException { + Connection session = Jsoup.newSession(); + + Document doc1 = session.newRequest() + .url(CookieServlet.Url) + .data(CookieServlet.LocationParam, EchoServlet.Url) + .data(CookieServlet.SetCookiesParam, "1") + .get(); + + // we should be redirected to the echo servlet with cookies + assertEquals(EchoServlet.Url, doc1.location()); + assertEchoServlet(doc1); // checks we only have /echo cookies + + Document doc2 = session.newRequest() + .url(EchoServlet.Url) + .get(); + assertEchoServlet(doc2); // test retained in session + + Document doc3 = session.newRequest() + .url(CookieServlet.Url) + .get(); + assertCookieServlet(doc3); // and so were the /cookie cookies + } + + @Test + public void testCanChangeParsers() throws IOException { + Connection session = Jsoup.newSession().parser(Parser.xmlParser()); + + String xmlUrl = FileServlet.urlTo("/htmltests/xml-test.xml"); + String xmlVal = "<doc><val>One<val>Two</val>Three</val></doc>\n"; + + Document doc1 = session.newRequest().url(xmlUrl).get(); + assertEquals(xmlVal, doc1.html()); // not HTML normed, used XML parser + + Document doc2 = session.newRequest().parser(Parser.htmlParser()).url(xmlUrl).get(); + assertTrue(doc2.html().startsWith("<html>")); + + Document doc3 = session.newRequest().url(xmlUrl).get(); + assertEquals(xmlVal, doc3.html()); // did not blow away xml default + } +} diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java index a4fb2b9660..6af7c21a10 100644 --- a/src/test/java/org/jsoup/integration/TestServer.java +++ b/src/test/java/org/jsoup/integration/TestServer.java @@ -34,7 +34,7 @@ public static void start() { public static void stop() { synchronized (jetty) { - int count = latch.decrementAndGet(); + int count = latch.getAndDecrement(); if (count == 0) { try { jetty.stop(); diff --git a/src/test/java/org/jsoup/integration/servlets/CookieServlet.java b/src/test/java/org/jsoup/integration/servlets/CookieServlet.java new file mode 100644 index 0000000000..2249f971d8 --- /dev/null +++ b/src/test/java/org/jsoup/integration/servlets/CookieServlet.java @@ -0,0 +1,76 @@ +package org.jsoup.integration.servlets; + +import org.jsoup.integration.TestServer; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +public class CookieServlet extends BaseServlet{ + public static final String Url = TestServer.map(CookieServlet.class); + public static final String SetCookiesParam = "setCookies"; + public static final String LocationParam = "loc"; + + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doIt(req, res); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doIt(req, res); + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + doIt(req, res); + } + + private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException { + // Do we want to set cookies? + if (req.getParameter(SetCookiesParam) != null) + setCookies(res); + + // Do we want to redirect elsewhere? + String loc = req.getParameter(LocationParam); + if (loc != null) { + res.sendRedirect(loc); + return; + } + + // print out the cookies that were received + res.setContentType(TextHtml); + res.setStatus(200); + + PrintWriter w = res.getWriter(); + w.println("<table>"); + final Cookie[] cookies = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + EchoServlet.write(w, cookie.getName(), cookie.getValue()); + } + } + w.println("</table>"); + } + + private void setCookies(HttpServletResponse res) { + Cookie one = new Cookie("One", "Root"); + one.setPath("/"); + res.addCookie(one); + + Cookie two = new Cookie("One", "CookieServlet"); + two.setPath("/CookieServlet"); + two.setHttpOnly(true); + two.setComment("Quite nice"); + res.addCookie(two); + + Cookie three = new Cookie("One", "EchoServlet"); + three.setPath("/EchoServlet"); + res.addCookie(three); + } + +} diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java index defb6e60a3..9931f9780e 100644 --- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java @@ -7,6 +7,7 @@ import javax.servlet.MultipartConfigElement; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; @@ -78,6 +79,14 @@ private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOExce } } + // cookies + final Cookie[] cookies = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + EchoServlet.write(w, "Cookie: " + cookie.getName(), cookie.getValue()); + } + } + // the request params Enumeration<String> parameterNames = req.getParameterNames(); while (parameterNames.hasMoreElements()) { @@ -111,7 +120,7 @@ private void doIt(HttpServletRequest req, HttpServletResponse res) throws IOExce w.println("</table>"); } - private static void write(PrintWriter w, String key, String val) { + static void write(PrintWriter w, String key, String val) { w.println("<tr><th>" + escape(key) + "</th><td>" + escape(val) + "</td></tr>"); } diff --git a/src/test/java/org/jsoup/integration/servlets/FileServlet.java b/src/test/java/org/jsoup/integration/servlets/FileServlet.java index 06ed2149d8..7447e8c777 100644 --- a/src/test/java/org/jsoup/integration/servlets/FileServlet.java +++ b/src/test/java/org/jsoup/integration/servlets/FileServlet.java @@ -25,6 +25,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE File file = ParseTest.getFile(location); if (file.exists()) { res.setContentType(contentType); + if (file.getName().endsWith("gz")) + res.addHeader("Content-Encoding", "gzip"); res.setStatus(HttpServletResponse.SC_OK); ServletOutputStream out = res.getOutputStream(); diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java index bccd8a97d4..e8db460073 100644 --- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java +++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java @@ -29,6 +29,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOE } long startTime = System.currentTimeMillis(); + w.println("<title>Slow Rider</title>"); while (true) { w.println("<p>Are you still there?"); boolean err = w.checkError(); // flush, and check still ok diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 97d4838522..cdd262349f 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -140,6 +140,11 @@ public class DocumentTest { assertEquals("http://www.nytimes.com/2010/07/26/business/global/26bp.html?hp",baseUri); } + @Test public void testLocationFromString() { + Document doc = Jsoup.parse("<p>Hello"); + assertEquals("", doc.location()); + } + @Test public void testHtmlAndXmlSyntax() { String h = "<!DOCTYPE html><body><img async checked='checked' src='&<>\"'>&lt;&gt;&amp;&quot;<foo />bar"; Document doc = Jsoup.parse(h); diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index a931efaf71..c64cbbfd20 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -2,8 +2,16 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; +import org.jsoup.integration.TestServer; +import org.jsoup.integration.servlets.CookieServlet; +import org.jsoup.integration.servlets.EchoServlet; +import org.jsoup.integration.servlets.FileServlet; +import org.jsoup.select.Elements; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -14,6 +22,16 @@ * @author Jonathan Hedley */ public class FormElementTest { + @BeforeAll + public static void setUp() { + TestServer.start(); + } + + @AfterAll + public static void tearDown() { + TestServer.stop(); + } + @Test public void hasAssociatedControls() { //"button", "fieldset", "input", "keygen", "object", "output", "select", "textarea" String html = "<form id=1><button id=1><fieldset id=2 /><input id=3><keygen id=4><object id=5><output id=6>" + @@ -175,4 +193,27 @@ public class FormElementTest { assertEquals("login", data.get(1).key()); assertNull(doc.selectFirst("input[name=pass]")); } + + @Test public void formSubmissionCarriesCookiesFromSession() throws IOException { + String echoUrl = EchoServlet.Url; // this is a dirty hack to initialize the EchoServlet(!) + Document cookieDoc = Jsoup.connect(CookieServlet.Url) + .data(CookieServlet.SetCookiesParam, "1") + .get(); + Document formDoc = cookieDoc.connection().newRequest() // carries cookies from above set + .url(FileServlet.urlTo("/htmltests/upload-form.html")) + .get(); + FormElement form = formDoc.select("form").forms().get(0); + Document echo = form.submit().post(); + + assertEquals(echoUrl, echo.location()); + Elements els = echo.select("th:contains(Cookie: One)"); + // ensure that the cookies are there and in path-specific order (two with same name) + assertEquals("EchoServlet", els.get(0).nextElementSibling().text()); + assertEquals("Root", els.get(1).nextElementSibling().text()); + + // make sure that the session following kept unique requests + assertTrue(cookieDoc.connection().response().url().toExternalForm().contains("CookieServlet")); + assertTrue(formDoc.connection().response().url().toExternalForm().contains("upload-form")); + assertTrue(echo.connection().response().url().toExternalForm().contains("EchoServlet")); + } } From 73e23c1aafe283c227b22993a9c9510d1113ac86 Mon Sep 17 00:00:00 2001 From: David Ehrmann <ehrmann@gmail.com> Date: Wed, 27 Jan 2021 01:47:22 -0800 Subject: [PATCH 514/774] Minor performance improvement to Selector.select() (#1479) --- src/main/java/org/jsoup/select/Selector.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index b564a06766..1ac4f1d881 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -117,20 +117,19 @@ public static Elements select(String query, Iterable<Element> roots) { Validate.notEmpty(query); Validate.notNull(roots); Evaluator evaluator = QueryParser.parse(query); - ArrayList<Element> elements = new ArrayList<>(); + Elements elements = new Elements(); IdentityHashMap<Element, Boolean> seenElements = new IdentityHashMap<>(); // dedupe elements by identity, not equality for (Element root : roots) { final Elements found = select(evaluator, root); for (Element el : found) { - if (!seenElements.containsKey(el)) { + if (seenElements.put(el, Boolean.TRUE) == null) { elements.add(el); - seenElements.put(el, Boolean.TRUE); } } } - return new Elements(elements); + return elements; } // exclude set. package open so that Elements can implement .not() selector. From ae9a18c9e1382b5d8bad14d09279eda725490c25 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sat, 30 Jan 2021 09:48:33 +1100 Subject: [PATCH 515/774] Clarified some Cleaner documentation --- src/main/java/org/jsoup/Jsoup.java | 8 +++++-- src/main/java/org/jsoup/safety/Safelist.java | 25 ++++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 3eb7f0b56c..a8f1f44726 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -246,10 +246,14 @@ public static String clean(String bodyHtml, String baseUri, Whitelist safelist) Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of permitted tags and attributes. - @param bodyHtml input untrusted HTML (body fragment) + <p>Note that as this method does not take a base href URL to resolve attributes with relative URLs against, those + URLs will be removed, unless the input HTML contains a {@code <base href> tag}. If you wish to preserve those, use + the {@link Jsoup#clean(String html, String baseHref, Safelist)} method instead, and enable + {@link Safelist#preserveRelativeLinks(boolean true)}.</p> + + @param bodyHtml input untrusted HTML (body fragment) @param safelist list of permitted HTML elements @return safe HTML (body fragment) - @see Cleaner#clean(Document) */ public static String clean(String bodyHtml, Safelist safelist) { diff --git a/src/main/java/org/jsoup/safety/Safelist.java b/src/main/java/org/jsoup/safety/Safelist.java index 91ad06b38c..76d56d2be2 100644 --- a/src/main/java/org/jsoup/safety/Safelist.java +++ b/src/main/java/org/jsoup/safety/Safelist.java @@ -34,23 +34,23 @@ Thank you to Ryan Grove (wonko.com) for the Ruby HTML cleaner http://github.com/ If you need to allow more through (please be careful!), tweak a base safelist with: </p> <ul> - <li>{@link #addTags} - <li>{@link #addAttributes} - <li>{@link #addEnforcedAttribute} - <li>{@link #addProtocols} + <li>{@link #addTags(String... tagNames)} + <li>{@link #addAttributes(String tagName, String... attributes)} + <li>{@link #addEnforcedAttribute(String tagName, String attribute, String value)} + <li>{@link #addProtocols(String tagName, String attribute, String... protocols)} </ul> <p> You can remove any setting from an existing safelist with: </p> <ul> - <li>{@link #removeTags} - <li>{@link #removeAttributes} - <li>{@link #removeEnforcedAttribute} - <li>{@link #removeProtocols} + <li>{@link #removeTags(String... tagNames)} + <li>{@link #removeAttributes(String tagName, String... attributes)} + <li>{@link #removeEnforcedAttribute(String tagName, String attribute)} + <li>{@link #removeProtocols(String tagName, String attribute, String... removeProtocols)} </ul> - + <p> - The cleaner and these safelist assume that you want to clean a <code>body</code> fragment of HTML (to add user + The cleaner and these safelists assume that you want to clean a <code>body</code> fragment of HTML (to add user supplied HTML into a templated page), and not to clean a full HTML document. If the latter is the case, either wrap the document HTML around the cleaned body HTML, or create a safelist that allows <code>html</code> and <code>head</code> elements as appropriate. @@ -58,10 +58,9 @@ If you need to allow more through (please be careful!), tweak a base safelist wi <p> If you are going to extend a safelist, please be very careful. Make sure you understand what attributes may lead to XSS attack vectors. URL attributes are particularly vulnerable and require careful validation. See - http://ha.ckers.org/xss.html for some XSS attack examples. + the <a href="https://owasp.org/www-community/xss-filter-evasion-cheatsheet">XSS Filter Evasion Cheat Sheet</a> for some + XSS attack examples (that jsoup will safegaurd against the default Cleaner and Safelist configuration). </p> - - @author Jonathan Hedley */ public class Safelist { private Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span] From 8a168eafd89ecbc109f811cfa2056f0722eaff0b Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Tue, 6 Jul 2021 22:22:14 +1000 Subject: [PATCH 516/774] Changelog note for new session support #1476 --- CHANGES | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGES b/CHANGES index 6616cea12b..316919bec2 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,27 @@ jsoup changelog you may need to make a code update. <https://github.com/jhy/jsoup/issues/1431> + * Change: the org.jsoup.Connection interface has been modified to introduce new methods for sessions and the cookie store. + If you have a custom implementation of this interface, you will need to add implementations of these methods. + (jsoup supports Java 7, so default interface implementations are not available.) + + * Improvement: added HTTP request session management support with Jsoup.newSession(). This extends the Connection + implementation to support (optional) sessions, which allow request defaults (timeout, proxy, etc) to be set once and + then applied to all requests within that session. + + Cookies are re-implemented to correctly support path and domain filtering when used within a session. A default + in-memory cookie store is used for the session, or a custom implementation (perhaps disk-persistent, or pre-set) + can be used instead. + + Forms submitted using the FormElement#submit() use the same session that was used to fetch the document and so pass + cookies and other defaults appropriately. + + The session is multi-thread safe and can execute multiple requests concurrently. If the user accidentally tries to + execute the same request object across multiple threads (vs calling Connection#newRequest()), + that is detected cleanly and a clear exception is thrown (vs weird blowups in input stream reading, or forcing + everything through a synchronized bottleneck. + <https://github.com/jhy/jsoup/pull/1476> + * Improvement: renamed the Whitelist class to Safelist, with the goal of more inclusive language. A shim is provided for backwards compatibility (source and binary). This shim is marked as deprecated and will be removed in the jsoup 1.15.1 release. From 528d85f3a99bf3a064eb8582af0643e1cc65f206 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Tue, 6 Jul 2021 23:06:24 +1000 Subject: [PATCH 517/774] Using the XML parser will default to XML output And make sure < in attributes are escaped when in XML output mode. Fixes #1420 --- CHANGES | 5 +++++ src/main/java/org/jsoup/nodes/Entities.java | 12 +++++++----- .../java/org/jsoup/parser/XmlTreeBuilder.java | 2 ++ .../java/org/jsoup/nodes/DocumentTest.java | 2 +- .../org/jsoup/parser/XmlTreeBuilderTest.java | 18 ++++++++++++++++++ 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 316919bec2..d5ef419060 100644 --- a/CHANGES +++ b/CHANGES @@ -118,6 +118,11 @@ jsoup changelog * Bugfix: if a HTML file ended with an open noscript tag, an "EOF" string would appear in the HTML output. + * Bugfix: when parsing a document as XML, automatically set the output syntax to XML, and ensure that "<" characters + in attributes are escaped as "&lt" (which is not required in HTML as the quoted attribute contents are safe, but is + required in XML). + <https://github.com/jhy/jsoup/issues/1420> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 7c0c15b66f..dee8d9f141 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -3,6 +3,7 @@ import org.jsoup.SerializationException; import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.nodes.Document.OutputSettings; import org.jsoup.parser.CharacterReader; import org.jsoup.parser.Parser; @@ -11,6 +12,7 @@ import java.util.Arrays; import java.util.HashMap; +import static org.jsoup.nodes.Document.OutputSettings.*; import static org.jsoup.nodes.Entities.EscapeMode.base; import static org.jsoup.nodes.Entities.EscapeMode.extended; @@ -24,7 +26,7 @@ public class Entities { static final int codepointRadix = 36; private static final char[] codeDelims = {',', ';'}; private static final HashMap<String, String> multipoints = new HashMap<>(); // name -> multiple character references - private static final Document.OutputSettings DefaultOutput = new Document.OutputSettings(); + private static final OutputSettings DefaultOutput = new OutputSettings(); public enum EscapeMode { /** @@ -135,7 +137,7 @@ public static int codepointsForName(final String name, final int[] codepoints) { * @param out the output settings to use * @return the escaped string */ - public static String escape(String string, Document.OutputSettings out) { + public static String escape(String string, OutputSettings out) { if (string == null) return ""; StringBuilder accum = StringUtil.borrowBuilder(); @@ -159,7 +161,7 @@ public static String escape(String string) { } // this method is ugly, and does a lot. but other breakups cause rescanning and stringbuilder generations - static void escape(Appendable accum, String string, Document.OutputSettings out, + static void escape(Appendable accum, String string, OutputSettings out, boolean inAttribute, boolean normaliseWhite, boolean stripLeadingWhite) throws IOException { boolean lastWasWhite = false; @@ -200,8 +202,8 @@ static void escape(Appendable accum, String string, Document.OutputSettings out, accum.append("&#xa0;"); break; case '<': - // escape when in character data or when in a xml attribue val; not needed in html attr val - if (!inAttribute || escapeMode == EscapeMode.xhtml) + // escape when in character data or when in a xml attribute val or XML syntax; not needed in html attr val + if (!inAttribute || escapeMode == EscapeMode.xhtml || out.syntax() == Syntax.xml) accum.append("&lt;"); else accum.append(c); diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 0b6d02d9d4..1c49f146ee 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -6,6 +6,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Entities; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; import org.jsoup.nodes.XmlDeclaration; @@ -33,6 +34,7 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) doc.outputSettings() .syntax(Document.OutputSettings.Syntax.xml) + .escapeMode(Entities.EscapeMode.xhtml) .prettyPrint(false); // as XML, we don't understand what whitespace is significant or not } diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index cdd262349f..47b7ccbede 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -163,7 +163,7 @@ public class DocumentTest { "<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + " <body>\n" + - " <img async=\"\" checked=\"checked\" src=\"&amp;<>&quot;\" />&lt;&gt;&amp;\"<foo />bar\n" + + " <img async=\"\" checked=\"checked\" src=\"&amp;&lt;>&quot;\" />&lt;&gt;&amp;\"<foo />bar\n" + " </body>\n" + "</html>", doc.html()); } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index b51755db29..d2527a9083 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -262,4 +262,22 @@ public void handlesLTinScript() { assertNull(treeBuilder.tokeniser); } + @Test public void xmlParserEnablesXmlOutputAndEscapes() { + // Test that when using the XML parser, the output mode and escape mode default to XHTML entities + // https://github.com/jhy/jsoup/issues/1420 + Document doc = Jsoup.parse("<p one='&lt;two&gt;&copy'>Three</p>", "", Parser.xmlParser()); + assertEquals(doc.outputSettings().syntax(), Syntax.xml); + assertEquals(doc.outputSettings().escapeMode(), Entities.EscapeMode.xhtml); + assertEquals("<p one=\"&lt;two>©\">Three</p>", doc.html()); // only the < should be escaped + } + + @Test public void xmlSyntaxEscapesLtInAttributes() { + // Regardless of the entity escape mode, make sure < is escaped in attributes when in XML + Document doc = Jsoup.parse("<p one='&lt;two&gt;&copy'>Three</p>", "", Parser.xmlParser()); + doc.outputSettings().escapeMode(Entities.EscapeMode.extended); + doc.outputSettings().charset("ascii"); // to make sure &copy; is output + assertEquals(doc.outputSettings().syntax(), Syntax.xml); + assertEquals("<p one=\"&lt;two>&copy;\">Three</p>", doc.html()); + } + } From 14419a6b6bd7be5d6a2b68f90edec48fd8032bf8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 7 Jul 2021 13:54:08 +1000 Subject: [PATCH 518/774] absUrl attributes should not recurse Fixes #1541 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Node.java | 8 +++---- .../org/jsoup/integration/FuzzFixesTest.java | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/FuzzFixesTest.java diff --git a/CHANGES b/CHANGES index d5ef419060..bcf56f3ce9 100644 --- a/CHANGES +++ b/CHANGES @@ -123,6 +123,10 @@ jsoup changelog required in XML). <https://github.com/jhy/jsoup/issues/1420> + * Bugfix: Fuzz testing: when parsing an attribute key containing "abs:abs", a validation error would be incorrectly + thrown. + <https://github.com/jhy/jsoup/issues/1541> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 216e5fdc28..8f7cda63ba 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -193,12 +193,10 @@ public void setBaseUri(final String baseUri) { */ public String absUrl(String attributeKey) { Validate.notEmpty(attributeKey); + if (!(hasAttributes() && attributes().hasKeyIgnoreCase(attributeKey))) // not using hasAttr, so that we don't recurse down hasAttr->absUrl + return ""; - if (!hasAttr(attributeKey)) { - return ""; // nothing to make absolute with - } else { - return StringUtil.resolve(baseUri(), attr(attributeKey)); - } + return StringUtil.resolve(baseUri(), attributes().getIgnoreCase(attributeKey)); } protected abstract List<Node> ensureChildNodes(); diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java new file mode 100644 index 0000000000..ddd9e35914 --- /dev/null +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -0,0 +1,22 @@ +package org.jsoup.integration; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup + */ +public class FuzzFixesTest { + + @Test + public void blankAbsAttr() { + // https://github.com/jhy/jsoup/issues/1541 + String html = "b<bodY abs: abs:abs: abs:abs:abs>"; + Document doc = Jsoup.parse(html); + assertNotNull(doc); + } +} From 33da0583c5dff6b20b291f4709addf2afbea8ecb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 7 Jul 2021 14:18:08 +1000 Subject: [PATCH 519/774] Fix potential NPE in resetInsertionMode() Fixes #1538 --- CHANGES | 5 ++++- .../java/org/jsoup/parser/HtmlTreeBuilder.java | 8 ++++---- .../java/org/jsoup/integration/FuzzFixesTest.java | 13 +++++++++++-- src/test/resources/fuzztests/1538.html | Bin 0 -> 287 bytes 4 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/fuzztests/1538.html diff --git a/CHANGES b/CHANGES index bcf56f3ce9..be4cd78b26 100644 --- a/CHANGES +++ b/CHANGES @@ -123,10 +123,13 @@ jsoup changelog required in XML). <https://github.com/jhy/jsoup/issues/1420> - * Bugfix: Fuzz testing: when parsing an attribute key containing "abs:abs", a validation error would be incorrectly + * Bugfix: [Fuzz] when parsing an attribute key containing "abs:abs", a validation error would be incorrectly thrown. <https://github.com/jhy/jsoup/issues/1541> + * Bugfix: [Fuzz] could NPE while parsing in resetInsertionMode(). + <https://github.com/jhy/jsoup/issues/1538> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 6d8c7142fb..c8c29d7a74 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -47,9 +47,9 @@ public class HtmlTreeBuilder extends TreeBuilder { private HtmlTreeBuilderState originalState; // original / marked state private boolean baseUriSetFromDoc; - private Element headElement; // the current head element - private FormElement formElement; // the current form element - private Element contextElement; // fragment parse context -- could be null even if fragment parsing + private @Nullable Element headElement; // the current head element + private @Nullable FormElement formElement; // the current form element + private @Nullable Element contextElement; // fragment parse context -- could be null even if fragment parsing private ArrayList<Element> formattingElements; // active (open) formatting elements private List<String> pendingTableCharacters; // chars in table to be shifted out private Token.EndTag emptyEnd; // reused empty end tag @@ -441,7 +441,7 @@ void resetInsertionMode() { last = true; node = contextElement; } - String name = node.normalName(); + String name = node != null ? node.normalName() : ""; if ("select".equals(name)) { transition(HtmlTreeBuilderState.InSelect); break; // frag diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index ddd9e35914..75452f1bad 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -2,8 +2,12 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.parser.HtmlParserTest; import org.junit.jupiter.api.Test; +import java.io.File; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -12,11 +16,16 @@ */ public class FuzzFixesTest { - @Test - public void blankAbsAttr() { + @Test public void blankAbsAttr() { // https://github.com/jhy/jsoup/issues/1541 String html = "b<bodY abs: abs:abs: abs:abs:abs>"; Document doc = Jsoup.parse(html); assertNotNull(doc); } + + @Test public void resetInsertionMode() throws IOException { + File in = ParseTest.getFile("/fuzztests/1538.html"); // lots of escape chars etc. + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + } } diff --git a/src/test/resources/fuzztests/1538.html b/src/test/resources/fuzztests/1538.html new file mode 100644 index 0000000000000000000000000000000000000000..2c5fd52a09383bfc1462a256b75842a58668008a GIT binary patch literal 287 zcmZvVF%H5o3`N7h&`WTI?&xEQr4ol=NN7YOg&H+x=)wW$?U<Q5_Yfpe1p;-lzy1FI z##=xP*Zl^KCx|vaNo~S53k#7QMvVymar1#JYGIS<WJ``@%5;3DIY(*8DBQaCdXt|@ zyGGMIk240i%b5ZW%&0o*xKb<C4ncavDsmYf;+iG7a2M1st3G~S40wRQ$O3^A;9a4V LDhd0doF?@K@FZkg literal 0 HcmV?d00001 From f84549e9fb5618cf58e37dc2f00adb8a279da371 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 7 Jul 2021 14:57:58 +1000 Subject: [PATCH 520/774] Don't recurse chasing XML declarations Fixes #1539 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Comment.java | 16 ++++++++++++++-- .../org/jsoup/integration/FuzzFixesTest.java | 13 +++++++++++++ src/test/resources/fuzztests/1539.html | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/fuzztests/1539.html diff --git a/CHANGES b/CHANGES index be4cd78b26..949dae0da4 100644 --- a/CHANGES +++ b/CHANGES @@ -130,6 +130,9 @@ jsoup changelog * Bugfix: [Fuzz] could NPE while parsing in resetInsertionMode(). <https://github.com/jhy/jsoup/issues/1538> + * Bugfix: [Fuzz] when parsing XML, could Stack Overflow when parsing XML declarations. + <https://github.com/jhy/jsoup/issues/1539> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index 0ecfa3a1bf..c3d0e73f21 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import java.io.IOException; +import java.util.regex.Pattern; /** A comment node. @@ -63,7 +64,12 @@ public Comment clone() { */ public boolean isXmlDeclaration() { String data = getData(); - return (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))); + return isXmlDeclarationData(data); + } + + private static final Pattern xmlDeclPattern = Pattern.compile("^[!?]xml.*", Pattern.CASE_INSENSITIVE); + private static boolean isXmlDeclarationData(String data) { + return data.length() > 4 && xmlDeclPattern.matcher(data).matches(); } /** @@ -72,8 +78,14 @@ public boolean isXmlDeclaration() { */ public @Nullable XmlDeclaration asXmlDeclaration() { String data = getData(); - Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri(), Parser.xmlParser()); + XmlDeclaration decl = null; + String declContent = data.substring(1, data.length() - 1); + // make sure this bogus comment is not packed with recursive xml decls; null out if so + if (isXmlDeclarationData(declContent)) + return null; + + Document doc = Jsoup.parse("<" + declContent + ">", baseUri(), Parser.xmlParser()); if (doc.children().size() > 0) { Element el = doc.child(0); decl = new XmlDeclaration(NodeUtils.parser(doc).settings().normalizeTag(el.tagName()), data.startsWith("!")); diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 75452f1bad..5cc98ecaf6 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -3,9 +3,11 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.parser.HtmlParserTest; +import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -28,4 +30,15 @@ public class FuzzFixesTest { Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } + + @Test public void xmlDeclOverflow() throws IOException { + File in = ParseTest.getFile("/fuzztests/1539.html"); // lots of escape chars etc. + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + + + } } diff --git a/src/test/resources/fuzztests/1539.html b/src/test/resources/fuzztests/1539.html new file mode 100644 index 0000000000..2dad90a933 --- /dev/null +++ b/src/test/resources/fuzztests/1539.html @@ -0,0 +1 @@ +/U!<!!!!!???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \ No newline at end of file From e718ddd847e15331d7d204a021b7f0aa80628a68 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 7 Jul 2021 15:34:57 +1000 Subject: [PATCH 521/774] Testcase for Fux #1542 - but no repro yet --- .../org/jsoup/integration/FuzzFixesTest.java | 12 ++++++++++++ src/test/resources/fuzztests/1542.html | Bin 0 -> 33506 bytes 2 files changed, 12 insertions(+) create mode 100644 src/test/resources/fuzztests/1542.html diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 5cc98ecaf6..1e456b137d 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -26,19 +26,31 @@ public class FuzzFixesTest { } @Test public void resetInsertionMode() throws IOException { + // https://github.com/jhy/jsoup/issues/1538 File in = ParseTest.getFile("/fuzztests/1538.html"); // lots of escape chars etc. Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } @Test public void xmlDeclOverflow() throws IOException { + // https://github.com/jhy/jsoup/issues/1539 File in = ParseTest.getFile("/fuzztests/1539.html"); // lots of escape chars etc. Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); + } + + @Test public void unconsume() throws IOException { + // https://github.com/jhy/jsoup/issues/1542 + File in = ParseTest.getFile("/fuzztests/1542.html"); // lots of escape chars etc. + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + Document docFromString = Jsoup.parse(ParseTest.getFileAsString(in)); + assertNotNull(docFromString); + // I haven't been able to replicate this - per code, content is handed as a plain string, as here. File encoding issue? Tried UTF8, ascii, windows-1252. } } diff --git a/src/test/resources/fuzztests/1542.html b/src/test/resources/fuzztests/1542.html new file mode 100644 index 0000000000000000000000000000000000000000..57cdc5b08b86e7cb39834cfdd54eea8bb20b31b9 GIT binary patch literal 33506 zcmeI)-Aw{P7=>Xwum-QZw1JH!*n^42km!}$n6V`a5#uH&;J6Hi(H(fW;S&fUujhP! zvoMUS@%iy~sJ^=z-b%Oe{%&|3UY^Fwi~9GPUU@a#tlmp+n#Zm;;p!oO{}A~U0fPk= zA&`hJ{*%QQ{d|INk$+E+V1Y#lB%%u}LLd=cU=ad|=mLunNOYJkjHy`^V1Y$xWkeTr zK^OD;R370G9^sMNgBSTizK}2E3;Dv)@?jf`?UQ(QL>*B_)Dd+=9Z^SqdkUbYrix;L zMX95p3%a0-`A(BZc!WoI#8)OhJ8|=fc_ai9(FGPEkccj@2!TX&fkg-;qKo6On5I@o zi5Xs7uvqWE^?HVJ-U@0VmR^;BMcX7o7pneosy?T3YH3$Z7j!`v^Jf@5!XrG=gGbJL zGTpu&p&^U?tBrN9lS@Nt3qexzLxC!uil^fBQ1LjGQ<v-1)32t*o@?&G7KYW2ZI-0H z{=V=|Kh{DY3+X)PY*Bm;3&m}@#f>_tlR6DazKpHEaGAm>U8XQKqY8Z60&i^Xcx){W zY>X4fV1Y$xJfI7@po{sjkw<uhM|h-mwIW~07xG0_zF?I<v5MVB$GeT4Bl*cP+jo|O b@evkSl*%t%&;?yowPmc*)++k3j{C6>5wex* literal 0 HcmV?d00001 From 32064f089cdd59e3ed3f54bff3e160d94523d5c7 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 7 Jul 2021 16:53:57 +1000 Subject: [PATCH 522/774] Bump jetty to 9.4.42 Jetty is used during test/build time only. --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index bc1cfed96b..d7b7e869c2 100644 --- a/pom.xml +++ b/pom.xml @@ -317,10 +317,10 @@ </dependency> <dependency> - <!-- jetty for webserver integration tests. 9.2 is last with Java7 support --> + <!-- jetty for webserver integration tests. 9.x is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.35.v20201120</version> + <version>9.4.42.v20210604</version> <scope>test</scope> </dependency> @@ -328,7 +328,7 @@ <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.35.v20201120</version> + <version>9.4.42.v20210604</version> <scope>test</scope> </dependency> From f8d4e12814cee17ab8ee1812692e86b7a867b695 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 7 Jul 2021 18:29:54 +1000 Subject: [PATCH 523/774] Updated parser rules for mis-nested tfoot tags Fixes #1543 --- CHANGES | 4 ++++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 7 ++++--- .../java/org/jsoup/integration/FuzzFixesTest.java | 7 +++++++ src/test/resources/fuzztests/1543.html | Bin 0 -> 319 bytes 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/fuzztests/1543.html diff --git a/CHANGES b/CHANGES index 949dae0da4..d488fec650 100644 --- a/CHANGES +++ b/CHANGES @@ -133,6 +133,10 @@ jsoup changelog * Bugfix: [Fuzz] when parsing XML, could Stack Overflow when parsing XML declarations. <https://github.com/jhy/jsoup/issues/1539> + * Bugfix: [Fuzz] fixed a potential Stack Overflow when parsing mis-nested tfoot tags, and updated the tree parser for + this situation to match the updated HTML5 spec. + <https://github.com/jhy/jsoup/issues/1543> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index cab017a171..41cce74ed0 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1214,12 +1214,13 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (name.equals("table")) { return handleMissingTr(t, tb); } else if (inSorted(name, InTableToBody)) { - if (!tb.inTableScope(name)) { + if (!tb.inTableScope(name) || !tb.inTableScope("tr")) { tb.error(this); return false; } - tb.processEndTag("tr"); - return tb.process(t); + tb.clearStackToTableRowContext(); + tb.pop(); // tr + tb.transition(InTableBody); } else if (inSorted(name, InRowIgnore)) { tb.error(this); return false; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 1e456b137d..69d04dbb82 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -53,4 +53,11 @@ public class FuzzFixesTest { // I haven't been able to replicate this - per code, content is handed as a plain string, as here. File encoding issue? Tried UTF8, ascii, windows-1252. } + + @Test public void stackOverflowState14() throws IOException { + // https://github.com/jhy/jsoup/issues/1543 + File in = ParseTest.getFile("/fuzztests/1543.html"); + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + } } diff --git a/src/test/resources/fuzztests/1543.html b/src/test/resources/fuzztests/1543.html new file mode 100644 index 0000000000000000000000000000000000000000..9f889b0c6a4b51a21549db1a64a96a812ee5777e GIT binary patch literal 319 zcmaivK?=e!6hxbq8&BX$5VDh?P!}>}={?#eu`RVJ=@;z6JM=Ju*E5L~MG$vC^WV&$ z>@{x1-PzWhD04?mgXuX3WqG4PN<tf5kQ5RbKq%MGNszIYq$fNYIcPY}uO5ru1?b#` zz4ST38hD0Fl3?s<Xf%V!tr{@0R%x$Rt13JuHODxc#+MSq(4*XJcgxtK15o2<QjIhf d(nlwA*7{pG{4w~N?|(_icuZBU{i^s8`~b>qVXy!I literal 0 HcmV?d00001 From ed83e92f7707cc361bc556fc1a4021e05315a7e4 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 7 Jul 2021 19:10:00 +1000 Subject: [PATCH 524/774] Limit the isElementInQueue check depth to 256 This prevents runaway nested formatting checks. Fixes #1544 --- CHANGES | 4 ++++ src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 5 ++++- src/test/java/org/jsoup/integration/FuzzFixesTest.java | 7 +++++++ src/test/resources/fuzztests/1544.html | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1544.html diff --git a/CHANGES b/CHANGES index d488fec650..89104515bd 100644 --- a/CHANGES +++ b/CHANGES @@ -137,6 +137,10 @@ jsoup changelog this situation to match the updated HTML5 spec. <https://github.com/jhy/jsoup/issues/1543> + * Bugfix: [Fuzz] fixed a potentially slow HTML parse when tags are nested extremely deep (e.g. 88K depth), by limiting + the formatting tag search depth to 256. In practice, it's generally between 4 - 8. + <https://github.com/jhy/jsoup/issues/1544> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index c8c29d7a74..c154802b89 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -322,8 +322,11 @@ boolean onStack(Element el) { return isElementInQueue(stack, el); } + private static final int maxQueueDepth = 256; // an arbitrary tension point between real HTML and crafted pain private boolean isElementInQueue(ArrayList<Element> queue, Element element) { - for (int pos = queue.size() -1; pos >= 0; pos--) { + final int bottom = queue.size() - 1; + final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; + for (int pos = bottom; pos >= upper; pos--) { Element next = queue.get(pos); if (next == element) { return true; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 69d04dbb82..d2dc86c582 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -60,4 +60,11 @@ public class FuzzFixesTest { Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } + + @Test public void parseTimeout() throws IOException { + // https://github.com/jhy/jsoup/issues/1544 + File in = ParseTest.getFile("/fuzztests/1544.html"); + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + } } diff --git a/src/test/resources/fuzztests/1544.html b/src/test/resources/fuzztests/1544.html new file mode 100644 index 0000000000..5837e8443a --- /dev/null +++ b/src/test/resources/fuzztests/1544.html @@ -0,0 +1 @@ +<i<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t==<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<m<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=e<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<|=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<magit=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<hgroup>=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=1<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t>>=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t= \ No newline at end of file From d19d96c57979a76229172c78b093792dbdf5dcb4 Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Thu, 8 Jul 2021 06:41:18 +0000 Subject: [PATCH 525/774] Java 16 build changed to release version. (#1503) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4f06da63b..36bb40b810 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time - java: [8, 11, 15, 16-ea] + java: [8, 11, 15, 16] fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: From 0acf488bbf47e48669617d49468be8698762428d Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Thu, 8 Jul 2021 06:42:19 +0000 Subject: [PATCH 526/774] CodeQL analysis added, including a weekly cron build. (#1494) --- .github/workflows/codeql.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..e9d6f12c60 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,35 @@ +name: CodeQL + +on: + push: + pull_request: + schedule: + - cron: '0 5 * * 3' + +jobs: + codeql: + runs-on: ubuntu-latest + name: "CodeQL" + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-java-codeql-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: CodeQL Initialization + uses: github/codeql-action/init@v1 + with: + languages: java + queries: +security-and-quality + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + - name: CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 48d86458bfef17e5ed1118235c422b7a9d4e5c7c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 9 Jul 2021 13:51:03 +1000 Subject: [PATCH 527/774] Don't try to unconsume after a potential bufferUp when moving from RCData to TagOpen state Should fix #1542 - I wasn't able to directly test this with the sample fuzz output, but was able to reconstruct the same code execution path and error with a standard unit test. Also reviewed all 14 other uses of unconsume() and verified that these all are directly after a consume() and there's no bufferUp() potential, so those cannot trigger this underrun. --- CHANGES | 4 +++ .../org/jsoup/parser/CharacterReader.java | 5 +++- .../java/org/jsoup/parser/TokeniserState.java | 3 +- .../org/jsoup/integration/FuzzFixesTest.java | 29 +++++++------------ .../org/jsoup/parser/TokeniserStateTest.java | 12 ++++++++ 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 89104515bd..c44379d514 100644 --- a/CHANGES +++ b/CHANGES @@ -141,6 +141,10 @@ jsoup changelog the formatting tag search depth to 256. In practice, it's generally between 4 - 8. <https://github.com/jhy/jsoup/issues/1544> + * Bugfix: [Fuzz] when parsing an unterminated RCDATA token (e.g. a <title> tag), could throw an IO Exception "No + buffer left to unconsume" when trying to rewind the buffer. + <https://github.com/jhy/jsoup/issues/1542> + *** Release 1.13.1 [2020-Feb-29] * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the selector. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 94710fd0e6..a781a0c2d9 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -137,9 +137,12 @@ char consume() { return val; } + /** + Unconsume one character (bufPos--). MUST only be called directly after a consume(), and no chance of a bufferUp. + */ void unconsume() { if (bufPos < 1) - throw new UncheckedIOException(new IOException("No buffer left to unconsume")); + throw new UncheckedIOException(new IOException("WTF: No buffer left to unconsume.")); // a bug if this fires, need to trace it. bufPos--; } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 74e96fa56f..e405c1b7f0 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -191,8 +191,7 @@ void read(Tokeniser t, CharacterReader r) { // consuming to EOF; break out here t.tagPending = t.createTagPending(false).name(t.appropriateEndTagName()); t.emitTagPending(); - r.unconsume(); // undo "<" - t.transition(Data); + t.transition(TagOpen); // straight into TagOpen, as we came from < and looks like we're on a start tag } else { t.emit("<"); t.transition(Rcdata); diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index d2dc86c582..5aeb7c9f9a 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -2,7 +2,6 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; -import org.jsoup.parser.HtmlParserTest; import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; @@ -11,28 +10,30 @@ import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; /** Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup */ public class FuzzFixesTest { - @Test public void blankAbsAttr() { + @Test + public void blankAbsAttr() { // https://github.com/jhy/jsoup/issues/1541 String html = "b<bodY abs: abs:abs: abs:abs:abs>"; Document doc = Jsoup.parse(html); assertNotNull(doc); } - @Test public void resetInsertionMode() throws IOException { + @Test + public void resetInsertionMode() throws IOException { // https://github.com/jhy/jsoup/issues/1538 File in = ParseTest.getFile("/fuzztests/1538.html"); // lots of escape chars etc. Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } - @Test public void xmlDeclOverflow() throws IOException { + @Test + public void xmlDeclOverflow() throws IOException { // https://github.com/jhy/jsoup/issues/1539 File in = ParseTest.getFile("/fuzztests/1539.html"); // lots of escape chars etc. Document doc = Jsoup.parse(in, "UTF-8"); @@ -42,26 +43,16 @@ public class FuzzFixesTest { assertNotNull(docXml); } - @Test public void unconsume() throws IOException { - // https://github.com/jhy/jsoup/issues/1542 - File in = ParseTest.getFile("/fuzztests/1542.html"); // lots of escape chars etc. - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docFromString = Jsoup.parse(ParseTest.getFileAsString(in)); - assertNotNull(docFromString); - - // I haven't been able to replicate this - per code, content is handed as a plain string, as here. File encoding issue? Tried UTF8, ascii, windows-1252. - } - - @Test public void stackOverflowState14() throws IOException { + @Test + public void stackOverflowState14() throws IOException { // https://github.com/jhy/jsoup/issues/1543 File in = ParseTest.getFile("/fuzztests/1543.html"); Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } - @Test public void parseTimeout() throws IOException { + @Test + public void parseTimeout() throws IOException { // https://github.com/jhy/jsoup/issues/1544 File in = ParseTest.getFile("/fuzztests/1544.html"); Document doc = Jsoup.parse(in, "UTF-8"); diff --git a/src/test/java/org/jsoup/parser/TokeniserStateTest.java b/src/test/java/org/jsoup/parser/TokeniserStateTest.java index d8a791f388..d34c38fcea 100644 --- a/src/test/java/org/jsoup/parser/TokeniserStateTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserStateTest.java @@ -218,6 +218,18 @@ public void testUnconsumeAtBufferBoundary() { assertEquals(CharacterReader.readAheadLimit - 1, errorList.get(0).getPosition()); } + @Test + public void testUnconsumeAfterBufferUp() { + // test for after consume() a bufferUp occurs (look-forward) but then attempts to unconsume. Would throw a "No buffer left to unconsume" + String triggeringSnippet = "<title>One <span>Two"; + char[] padding = new char[CharacterReader.readAheadLimit - triggeringSnippet.length() + 8]; // The "<span" part must be just at the limit. The "containsIgnoreCase" scan does a bufferUp, losing the unconsume + Arrays.fill(padding, ' '); + String paddedSnippet = String.valueOf(padding) + triggeringSnippet; + ParseErrorList errorList = ParseErrorList.tracking(1); + Parser.parseFragment(paddedSnippet, null, "", errorList); + // just asserting we don't get a WTF on unconsume + } + @Test public void testOpeningAngleBracketInsteadOfAttribute() { String triggeringSnippet = "<html <"; From f49f92c4c29cc2a8440c4ffccc2fb248cdd30694 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 9 Jul 2021 15:38:27 +1000 Subject: [PATCH 528/774] More robust prevention of XML Declaration recursion --- src/main/java/org/jsoup/nodes/Comment.java | 16 ++++++++-------- .../org/jsoup/integration/FuzzFixesTest.java | 11 +++++++++++ src/test/resources/fuzztests/1569.html | Bin 0 -> 317288 bytes 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 src/test/resources/fuzztests/1569.html diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java index c3d0e73f21..33f5c1915c 100644 --- a/src/main/java/org/jsoup/nodes/Comment.java +++ b/src/main/java/org/jsoup/nodes/Comment.java @@ -1,11 +1,10 @@ package org.jsoup.nodes; -import org.jsoup.Jsoup; +import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; import javax.annotation.Nullable; import java.io.IOException; -import java.util.regex.Pattern; /** A comment node. @@ -67,9 +66,8 @@ public boolean isXmlDeclaration() { return isXmlDeclarationData(data); } - private static final Pattern xmlDeclPattern = Pattern.compile("^[!?]xml.*", Pattern.CASE_INSENSITIVE); private static boolean isXmlDeclarationData(String data) { - return data.length() > 4 && xmlDeclPattern.matcher(data).matches(); + return (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))); } /** @@ -81,13 +79,15 @@ private static boolean isXmlDeclarationData(String data) { XmlDeclaration decl = null; String declContent = data.substring(1, data.length() - 1); - // make sure this bogus comment is not packed with recursive xml decls; null out if so + // make sure this bogus comment is not immediately followed by another, treat as comment if so if (isXmlDeclarationData(declContent)) return null; - Document doc = Jsoup.parse("<" + declContent + ">", baseUri(), Parser.xmlParser()); - if (doc.children().size() > 0) { - Element el = doc.child(0); + String fragment = "<" + declContent + ">"; + // use the HTML parser not XML, so we don't get into a recursive XML Declaration on contrived data + Document doc = Parser.htmlParser().settings(ParseSettings.preserveCase).parseInput(fragment, baseUri()); + if (doc.body().children().size() > 0) { + Element el = doc.body().child(0); decl = new XmlDeclaration(NodeUtils.parser(doc).settings().normalizeTag(el.tagName()), data.startsWith("!")); decl.attributes().addAll(el.attributes()); } diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 5aeb7c9f9a..cff3333084 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -43,6 +43,17 @@ public void xmlDeclOverflow() throws IOException { assertNotNull(docXml); } + @Test + public void xmlDeclOverflowOOM() throws IOException { + // https://github.com/jhy/jsoup/issues/1569 + File in = ParseTest.getFile("/fuzztests/1569.html"); + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } + @Test public void stackOverflowState14() throws IOException { // https://github.com/jhy/jsoup/issues/1543 diff --git a/src/test/resources/fuzztests/1569.html b/src/test/resources/fuzztests/1569.html new file mode 100644 index 0000000000000000000000000000000000000000..0efe6370ce1acf628cd7bff84ecd0392e8eb99ff GIT binary patch literal 317288 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 literal 0 HcmV?d00001 From e45e53c849197be70364a63db8a7c6c8971c1ead Mon Sep 17 00:00:00 2001 From: Hulmes <60814832+suarez12138@users.noreply.github.com> Date: Fri, 9 Jul 2021 14:37:27 +0800 Subject: [PATCH 529/774] Complete adoption agency algorithm (#1517) Follow adoption agency algorithm --- .../org/jsoup/parser/HtmlTreeBuilder.java | 19 ++++++- .../jsoup/parser/HtmlTreeBuilderState.java | 11 ++-- .../parser/HtmlTreeBuilderStateTest.java | 51 +++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index c154802b89..dcfef80283 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -613,6 +613,14 @@ Element lastFormattingElement() { return formattingElements.size() > 0 ? formattingElements.get(formattingElements.size()-1) : null; } + int positionOfElement(Element el){ + for (int i = 0; i < formattingElements.size(); i++){ + if (el == formattingElements.get(i)) + return i; + } + return -1; + } + Element removeLastFormattingElement() { int size = formattingElements.size(); if (size > 0) @@ -623,6 +631,16 @@ Element removeLastFormattingElement() { // active formatting elements void pushActiveFormattingElements(Element in) { + this.checkActiveFormattingElements(in); + formattingElements.add(in); + } + + void pushWithBookmark(Element in,int bookmark){ + this.checkActiveFormattingElements(in); + formattingElements.add(bookmark, in); + } + + void checkActiveFormattingElements(Element in){ int numSeen = 0; for (int pos = formattingElements.size() -1; pos >= 0; pos--) { Element el = formattingElements.get(pos); @@ -637,7 +655,6 @@ void pushActiveFormattingElements(Element in) { break; } } - formattingElements.add(in); } private boolean isSameFormattingElement(Element a, Element b) { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 41cce74ed0..cec10b4561 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -806,11 +806,14 @@ else if (!tb.onStack(formatEl)) { // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents // run-aways final int stackSize = stack.size(); + int bookmark = -1; for (int si = 0; si < stackSize && si < 64; si++) { el = stack.get(si); if (el == formatEl) { commonAncestor = stack.get(si - 1); seenFormattingElement = true; + // Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. + bookmark = tb.positionOfElement(el); } else if (seenFormattingElement && tb.isSpecial(el)) { furthestBlock = el; break; @@ -822,8 +825,6 @@ else if (!tb.onStack(formatEl)) { return true; } - // todo: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. - // does that mean: int pos of format el in list? Element node = furthestBlock; Element lastNode = furthestBlock; for (int j = 0; j < 3; j++) { @@ -843,8 +844,9 @@ else if (!tb.onStack(formatEl)) { //noinspection StatementWithEmptyBody if (lastNode == furthestBlock) { - // todo: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. + // move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. // not getting how this bookmark both straddles the element above, but is inbetween here... + bookmark = tb.positionOfElement(node) + 1; } if (lastNode.parent() != null) lastNode.remove(); @@ -871,7 +873,8 @@ else if (!tb.onStack(formatEl)) { } furthestBlock.appendChild(adopter); tb.removeFromActiveFormattingElements(formatEl); - // todo: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. + // insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. + tb.pushWithBookmark(adopter, bookmark); tb.removeFromStack(formatEl); tb.insertOnStackAfter(furthestBlock, adopter); } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 3aea3bdfd1..649340c358 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -1,5 +1,6 @@ package org.jsoup.parser; +import org.jsoup.Jsoup; import org.jsoup.parser.HtmlTreeBuilderState.Constants; import org.junit.jupiter.api.Test; @@ -46,4 +47,54 @@ public void ensureArraysAreSorted() { assertEquals(38, constants.size()); } + + @Test + public void nestedAnchorElements01() { + String html = "<html>\n" + + " <body>\n" + + " <a href='#1'>\n" + + " <div>\n" + + " <a href='#2'>child</a>\n" + + " </div>\n" + + " </a>\n" + + " </body>\n" + + "</html>"; + String s = Jsoup.parse(html).toString(); + assertEquals("<html> \n" + + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + + " <body> <a href=\"#1\"> </a>\n" + + " <div>\n" + + " <a href=\"#1\"> </a><a href=\"#2\">child</a> \n" + + " </div> \n" + + " </body>\n" + + "</html>", s); + } + + @Test + public void nestedAnchorElements02() { + String html = "<html>\n" + + " <body>\n" + + " <a href='#1'>\n" + + " <div>\n" + + " <div>\n" + + " <a href='#2'>child</a>\n" + + " </div>\n" + + " </div>\n" + + " </a>\n" + + " </body>\n" + + "</html>"; + String s = Jsoup.parse(html).toString(); + assertEquals("<html> \n" + + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + + " <body> <a href=\"#1\"> </a>\n" + + " <div>\n" + + " <a href=\"#1\"> </a>\n" + + " <div>\n" + + " <a href=\"#1\"> </a><a href=\"#2\">child</a> \n" + + " </div> \n" + + " </div> \n" + + " </body>\n" + + "</html>", s); + } + } From 661523f261c750ffa2a6148316b3946e30349558 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 9 Jul 2021 16:41:59 +1000 Subject: [PATCH 530/774] Changelog for adoption agency fix Fixes #845, #1517 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index c44379d514..7448efafa0 100644 --- a/CHANGES +++ b/CHANGES @@ -79,6 +79,10 @@ jsoup changelog * Build Improvement: added nullability annotations and initial settings. <https://github.com/jhy/jsoup/pull/1467> + * Bugfix: corrected the adoption agency algorithm, to handle cases where e.g. a <a> tag incorrectly nests further <a> + tags. + <https://github.com/jhy/jsoup/pull/1517> <https://github.com/jhy/jsoup/issues/845> + * Bugfix: when parsing HTML, could throw NPEs on some tags (isindex or table>input). <https://github.com/jhy/jsoup/issues/1404> From 8db724e82cc73f921adc2fad900e10655438e662 Mon Sep 17 00:00:00 2001 From: Yohei Kishimoto <morokosi@users.noreply.github.com> Date: Fri, 9 Jul 2021 17:42:19 +0900 Subject: [PATCH 531/774] resolve abnormal urls in compliance with rfc3986 (#1482) --- .../java/org/jsoup/internal/StringUtil.java | 10 +++++++--- .../org/jsoup/internal/StringUtilTest.java | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index f23c872358..53be1f9570 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.Stack; +import java.util.regex.Pattern; /** A minimal String utility class. Designed for <b>internal</b> jsoup use only - the API and outcome may change without @@ -259,6 +260,7 @@ public static boolean isAscii(String string) { return true; } + private static Pattern extraDotSegmentsPattern = Pattern.compile("^/((\\.{1,2}/)+)"); /** * Create a new absolute URL, from a provided existing absolute URL and a relative URL component. * @param base the existing absolute base URL @@ -271,10 +273,12 @@ public static URL resolve(URL base, String relUrl) throws MalformedURLException if (relUrl.startsWith("?")) relUrl = base.getPath() + relUrl; // workaround: //example.com + ./foo = //example.com/./foo, not //example.com/foo - if (relUrl.indexOf('.') == 0 && base.getFile().indexOf('/') != 0) { - base = new URL(base.getProtocol(), base.getHost(), base.getPort(), "/" + base.getFile()); + URL url = new URL(base, relUrl); + String fixedFile = extraDotSegmentsPattern.matcher(url.getFile()).replaceFirst("/"); + if (url.getRef() != null) { + fixedFile = fixedFile + "#" + url.getRef(); } - return new URL(base, relUrl); + return new URL(url.getProtocol(), url.getHost(), url.getPort(), fixedFile); } /** diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index 55af0ff836..1956084bae 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -99,6 +99,25 @@ public void join() { assertEquals("ftp://example.com/one", resolve("ftp://example.com/two/", "../one")); assertEquals("ftp://example.com/one/two.c", resolve("ftp://example.com/one/", "./two.c")); assertEquals("ftp://example.com/one/two.c", resolve("ftp://example.com/one/", "two.c")); + // examples taken from rfc3986 section 5.4.2 + assertEquals("http://example.com/g", resolve("http://example.com/b/c/d;p?q", "../../../g")); + assertEquals("http://example.com/g", resolve("http://example.com/b/c/d;p?q", "../../../../g")); + assertEquals("http://example.com/g", resolve("http://example.com/b/c/d;p?q", "/./g")); + assertEquals("http://example.com/g", resolve("http://example.com/b/c/d;p?q", "/../g")); + assertEquals("http://example.com/b/c/g.", resolve("http://example.com/b/c/d;p?q", "g.")); + assertEquals("http://example.com/b/c/.g", resolve("http://example.com/b/c/d;p?q", ".g")); + assertEquals("http://example.com/b/c/g..", resolve("http://example.com/b/c/d;p?q", "g..")); + assertEquals("http://example.com/b/c/..g", resolve("http://example.com/b/c/d;p?q", "..g")); + assertEquals("http://example.com/b/g", resolve("http://example.com/b/c/d;p?q", "./../g")); + assertEquals("http://example.com/b/c/g/", resolve("http://example.com/b/c/d;p?q", "./g/.")); + assertEquals("http://example.com/b/c/g/h", resolve("http://example.com/b/c/d;p?q", "g/./h")); + assertEquals("http://example.com/b/c/h", resolve("http://example.com/b/c/d;p?q", "g/../h")); + assertEquals("http://example.com/b/c/g;x=1/y", resolve("http://example.com/b/c/d;p?q", "g;x=1/./y")); + assertEquals("http://example.com/b/c/y", resolve("http://example.com/b/c/d;p?q", "g;x=1/../y")); + assertEquals("http://example.com/b/c/g?y/./x", resolve("http://example.com/b/c/d;p?q", "g?y/./x")); + assertEquals("http://example.com/b/c/g?y/../x", resolve("http://example.com/b/c/d;p?q", "g?y/../x")); + assertEquals("http://example.com/b/c/g#s/./x", resolve("http://example.com/b/c/d;p?q", "g#s/./x")); + assertEquals("http://example.com/b/c/g#s/../x", resolve("http://example.com/b/c/d;p?q", "g#s/../x")); } @Test From 87085a830179877087dd0ae4480b53555b42a80e Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Fri, 9 Jul 2021 18:44:42 +1000 Subject: [PATCH 532/774] Changelog for #1482 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 7448efafa0..fcab61cd56 100644 --- a/CHANGES +++ b/CHANGES @@ -72,6 +72,9 @@ jsoup changelog * Improvement: added Element#insertChildren and Elment#prependChildren, as convenience methods in addition to Element#insertChildren(index, children), for bulk moving nodes. + * Improvement: clean up relative URLs with too many .. segments better. + <https://github.com/jhy/jsoup/pull/1482> + * Build Improvement: moved to GitHub Workflows for build verification. * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. From cedf83c47f5fe58c4c9b2514c864bc2d576321d4 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Fri, 9 Jul 2021 19:07:51 +1000 Subject: [PATCH 533/774] Cleanup UTF BOM recognition --- src/main/java/org/jsoup/helper/HttpConnection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 7914803ada..16d3fd7954 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -490,8 +490,10 @@ private static String fixHeaderEncoding(String val) { private static boolean looksLikeUtf8(byte[] input) { int i = 0; // BOM: - if (input.length >= 3 && (input[0] & 0xFF) == 0xEF - && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) { + if (input.length >= 3 + && (input[0] & 0xFF) == 0xEF + && (input[1] & 0xFF) == 0xBB + && (input[2] & 0xFF) == 0xBF) { i = 3; } From 0a5a7ef1c69319cb2fd3f70eb0c9c85e5c972d71 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Fri, 9 Jul 2021 20:07:42 +1000 Subject: [PATCH 534/774] Minor cleanup --- src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/main/java/org/jsoup/nodes/DataNode.java | 4 +++- src/main/java/org/jsoup/nodes/Node.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 3bbc24a84e..fa1331a4ee 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -174,7 +174,7 @@ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { protected static boolean shouldCollapseAttribute(final String key, @Nullable final String val, final Document.OutputSettings out) { return ( out.syntax() == Document.OutputSettings.Syntax.html && - (val == null || ("".equals(val) || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); + (val == null || (val.isEmpty() || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); } /** diff --git a/src/main/java/org/jsoup/nodes/DataNode.java b/src/main/java/org/jsoup/nodes/DataNode.java index 336cc6a310..2c37e75661 100644 --- a/src/main/java/org/jsoup/nodes/DataNode.java +++ b/src/main/java/org/jsoup/nodes/DataNode.java @@ -57,9 +57,11 @@ public DataNode clone() { /** Create a new DataNode from HTML encoded data. @param encodedData encoded data - @param baseUri bass URI + @param baseUri base URI @return new DataNode + @deprecated Unused, and will be removed in 1.15.1. */ + @Deprecated public static DataNode createFromEncoded(String encodedData, String baseUri) { String data = Entities.unescape(encodedData); return new DataNode(data); diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 8f7cda63ba..0efe532da2 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -111,7 +111,7 @@ public boolean hasAttr(String attributeKey) { if (attributeKey.startsWith("abs:")) { String key = attributeKey.substring("abs:".length()); - if (attributes().hasKeyIgnoreCase(key) && !absUrl(key).equals("")) + if (attributes().hasKeyIgnoreCase(key) && !absUrl(key).isEmpty()) return true; } return attributes().hasKeyIgnoreCase(attributeKey); From f0345613a58ff6b9699b051ad7bcf8105529355b Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Fri, 9 Jul 2021 20:21:40 +1000 Subject: [PATCH 535/774] Code cleanup --- .../jsoup/parser/HtmlTreeBuilderState.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index cec10b4561..518793c246 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -8,6 +8,7 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import javax.annotation.Nullable; import java.util.ArrayList; import static org.jsoup.internal.StringUtil.inSorted; @@ -123,7 +124,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (name.equals("base") && el.hasAttr("href")) tb.maybeSetBaseUri(el); } else if (name.equals("meta")) { - Element meta = tb.insertEmpty(start); + tb.insertEmpty(start); // todo: charset switches } else if (name.equals("title")) { handleRcData(start, tb); @@ -842,7 +843,6 @@ else if (!tb.onStack(formatEl)) { tb.replaceOnStack(node, replacement); node = replacement; - //noinspection StatementWithEmptyBody if (lastNode == furthestBlock) { // move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. // not getting how this bookmark both straddles the element above, but is inbetween here... @@ -855,14 +855,16 @@ else if (!tb.onStack(formatEl)) { lastNode = node; } - if (inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { - if (lastNode.parent() != null) - lastNode.remove(); - tb.insertInFosterParent(lastNode); - } else { - if (lastNode.parent() != null) - lastNode.remove(); - commonAncestor.appendChild(lastNode); + if (commonAncestor != null) { // safety check, but would be an error if null + if (inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { + if (lastNode.parent() != null) + lastNode.remove(); + tb.insertInFosterParent(lastNode); + } else { + if (lastNode.parent() != null) + lastNode.remove(); + commonAncestor.appendChild(lastNode); + } } Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); From 0bd588555f088b550ee5b046c34bb1ae8ae33e3f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 10 Jul 2021 11:44:50 +1000 Subject: [PATCH 536/774] Release prep changelog update --- CHANGES | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index fcab61cd56..9ff4ae7f53 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.14.1 [PENDING] +*** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. * Change: updated the minimum Android API level from 8 to 10. @@ -13,9 +13,8 @@ jsoup changelog you may need to make a code update. <https://github.com/jhy/jsoup/issues/1431> - * Change: the org.jsoup.Connection interface has been modified to introduce new methods for sessions and the cookie store. - If you have a custom implementation of this interface, you will need to add implementations of these methods. - (jsoup supports Java 7, so default interface implementations are not available.) + * Change: the org.jsoup.Connection interface has been modified to introduce new methods for sessions and the cookie + store. If you have a custom implementation of this interface, you will need to add implementations of these methods. * Improvement: added HTTP request session management support with Jsoup.newSession(). This extends the Connection implementation to support (optional) sessions, which allow request defaults (timeout, proxy, etc) to be set once and @@ -75,9 +74,16 @@ jsoup changelog * Improvement: clean up relative URLs with too many .. segments better. <https://github.com/jhy/jsoup/pull/1482> + * Build Improvement: integrated jsoup into the OSS Fuzz project, which semi-randomly generates millions of different + HTML and XML input files, searching for areas to improve in the parser for increased robustness and throughput. + <https://github.com/jhy/jsoup/issues/1502> + + * Build Improvement: integrated with GitHub's CodeQL static code analyzer. + <https://github.com/jhy/jsoup/pull/1494> + * Build Improvement: moved to GitHub Workflows for build verification. - * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.35.v2020112. + * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.42. * Build Improvement: added nullability annotations and initial settings. <https://github.com/jhy/jsoup/pull/1467> From b24f2e463231269e9bc1e9118a70b9fd654f1a2f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 10 Jul 2021 12:14:03 +1000 Subject: [PATCH 537/774] [maven-release-plugin] prepare release jsoup-1.14.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d7b7e869c2..8816fc3203 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.1-SNAPSHOT</version> + <version>1.14.1</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.14.1</tag> </scm> <organization> <name>Jonathan Hedley</name> From d3c4a2ee87b7f6d08e7754e934495ff7693601c8 Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Sat, 10 Jul 2021 08:00:40 +0000 Subject: [PATCH 538/774] Dependabot configuration (#1570) --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..e365683549 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 + +updates: + - package-ecosystem: maven + directory: / + schedule: + interval: weekly From a067d5a25f46fe88219a2eb3f4c6c4d5961eeee4 Mon Sep 17 00:00:00 2001 From: Pascal Schumacher <pascalschumacher@gmx.net> Date: Sat, 10 Jul 2021 10:12:13 +0200 Subject: [PATCH 539/774] Switch to faster String#indexOf(char) method. (#1348) --- src/main/java/org/jsoup/helper/W3CDom.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 829c8fa9f5..d3dae0c5e5 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -283,7 +283,7 @@ private String updateNamespaces(org.jsoup.nodes.Element el) { } // get the element prefix if any - int pos = el.tagName().indexOf(":"); + int pos = el.tagName().indexOf(':'); return pos > 0 ? el.tagName().substring(0, pos) : ""; } From 0e7938caaf960dbe6a5dd0318cd5689f32bf9383 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 12:55:32 +1000 Subject: [PATCH 540/774] Attempt to de-dupe GitHub Actions flows for PRs --- .github/workflows/build.yml | 6 +++++- .github/workflows/codeql.yml | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36bb40b810..1dd5333cee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,9 @@ name: Build -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: jobs: test: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e9d6f12c60..f387b2f721 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,6 +2,8 @@ name: CodeQL on: push: + branches: + - master pull_request: schedule: - cron: '0 5 * * 3' From 3a2ce47fc83279d2ee14297dd3dd0e180c26210c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:05:28 +1000 Subject: [PATCH 541/774] Bump maven-bundle-plugin from 2.5.4 to 5.1.2 (#1575) Bumps maven-bundle-plugin from 2.5.4 to 5.1.2. --- updated-dependencies: - dependency-name: org.apache.felix:maven-bundle-plugin dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8816fc3203..fe0abbfc83 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> - <version>2.5.4</version> + <version>5.1.2</version> <executions> <execution> <id>bundle-manifest</id> From cd40d54231ca8cfb9046180d6bfa8d7f4832779d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:09:49 +1000 Subject: [PATCH 542/774] Bump maven-compiler-plugin from 3.8.0 to 3.8.1 (#1573) Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.8.0 to 3.8.1. - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.0...maven-compiler-plugin-3.8.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fe0abbfc83..16dbe64c0b 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.8.0</version> + <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> From 5f6830bcaf46448e2b6fe5f862669c19624dbd89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:10:12 +1000 Subject: [PATCH 543/774] Bump gson from 2.7 to 2.8.7 (#1572) Bumps [gson](https://github.com/google/gson) from 2.7 to 2.8.7. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.7...gson-parent-2.8.7) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 16dbe64c0b..c53ad0d66b 100644 --- a/pom.xml +++ b/pom.xml @@ -312,7 +312,7 @@ <!-- gson, to fetch entities from w3.org --> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>2.7</version> + <version>2.8.7</version> <scope>test</scope> </dependency> From 2e5bf64bcd69452bd9cfe5b469c58149d5d0dd27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:10:39 +1000 Subject: [PATCH 544/774] Bump maven-failsafe-plugin from 3.0.0-M3 to 3.0.0-M5 (#1571) Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M3 to 3.0.0-M5. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M3...surefire-3.0.0-M5) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c53ad0d66b..fa1f201206 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,7 @@ </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> - <version>3.0.0-M3</version> + <version>3.0.0-M5</version> <executions> <execution> <goals> @@ -283,7 +283,7 @@ <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> - <version>2.22.0</version> + <version>3.0.0-M5</version> <executions> <execution> <goals> From b1f70e9dc3fc06b8e1e7a320819eafe6cddb5c51 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 13:11:42 +1000 Subject: [PATCH 545/774] Prep POM for 1.14.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8816fc3203..c20c91125e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.1</version> + <version>1.14.2-SNAPSHOT</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>jsoup-1.14.1</tag> + <tag>HEAD</tag> </scm> <organization> <name>Jonathan Hedley</name> From 5986ca1d8ed0c68a35f42ea0b919d8ec66c3294e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 13:28:20 +1000 Subject: [PATCH 546/774] Zip the fuzz test files --- .../org/jsoup/integration/FuzzFixesTest.java | 10 +++++----- src/test/resources/fuzztests/1538.html | Bin 287 -> 0 bytes src/test/resources/fuzztests/1538.html.gz | Bin 0 -> 186 bytes src/test/resources/fuzztests/1539.html | 1 - src/test/resources/fuzztests/1539.html.gz | Bin 0 -> 73 bytes src/test/resources/fuzztests/1542.html | Bin 33506 -> 0 bytes src/test/resources/fuzztests/1542.html.gz | Bin 0 -> 305 bytes src/test/resources/fuzztests/1543.html | Bin 319 -> 0 bytes src/test/resources/fuzztests/1543.html.gz | Bin 0 -> 203 bytes src/test/resources/fuzztests/1544.html | 1 - src/test/resources/fuzztests/1544.html.gz | Bin 0 -> 366 bytes src/test/resources/fuzztests/1569.html | Bin 317288 -> 0 bytes src/test/resources/fuzztests/1569.html.gz | Bin 0 -> 2006 bytes 13 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 src/test/resources/fuzztests/1538.html create mode 100644 src/test/resources/fuzztests/1538.html.gz delete mode 100644 src/test/resources/fuzztests/1539.html create mode 100644 src/test/resources/fuzztests/1539.html.gz delete mode 100644 src/test/resources/fuzztests/1542.html create mode 100644 src/test/resources/fuzztests/1542.html.gz delete mode 100644 src/test/resources/fuzztests/1543.html create mode 100644 src/test/resources/fuzztests/1543.html.gz delete mode 100644 src/test/resources/fuzztests/1544.html create mode 100644 src/test/resources/fuzztests/1544.html.gz delete mode 100644 src/test/resources/fuzztests/1569.html create mode 100644 src/test/resources/fuzztests/1569.html.gz diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index cff3333084..b85bb17672 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -27,7 +27,7 @@ public void blankAbsAttr() { @Test public void resetInsertionMode() throws IOException { // https://github.com/jhy/jsoup/issues/1538 - File in = ParseTest.getFile("/fuzztests/1538.html"); // lots of escape chars etc. + File in = ParseTest.getFile("/fuzztests/1538.html.gz"); // lots of escape chars etc. Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } @@ -35,7 +35,7 @@ public void resetInsertionMode() throws IOException { @Test public void xmlDeclOverflow() throws IOException { // https://github.com/jhy/jsoup/issues/1539 - File in = ParseTest.getFile("/fuzztests/1539.html"); // lots of escape chars etc. + File in = ParseTest.getFile("/fuzztests/1539.html.gz"); // lots of escape chars etc. Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); @@ -46,7 +46,7 @@ public void xmlDeclOverflow() throws IOException { @Test public void xmlDeclOverflowOOM() throws IOException { // https://github.com/jhy/jsoup/issues/1569 - File in = ParseTest.getFile("/fuzztests/1569.html"); + File in = ParseTest.getFile("/fuzztests/1569.html.gz"); Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); @@ -57,7 +57,7 @@ public void xmlDeclOverflowOOM() throws IOException { @Test public void stackOverflowState14() throws IOException { // https://github.com/jhy/jsoup/issues/1543 - File in = ParseTest.getFile("/fuzztests/1543.html"); + File in = ParseTest.getFile("/fuzztests/1543.html.gz"); Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } @@ -65,7 +65,7 @@ public void stackOverflowState14() throws IOException { @Test public void parseTimeout() throws IOException { // https://github.com/jhy/jsoup/issues/1544 - File in = ParseTest.getFile("/fuzztests/1544.html"); + File in = ParseTest.getFile("/fuzztests/1544.html.gz"); Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } diff --git a/src/test/resources/fuzztests/1538.html b/src/test/resources/fuzztests/1538.html deleted file mode 100644 index 2c5fd52a09383bfc1462a256b75842a58668008a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287 zcmZvVF%H5o3`N7h&`WTI?&xEQr4ol=NN7YOg&H+x=)wW$?U<Q5_Yfpe1p;-lzy1FI z##=xP*Zl^KCx|vaNo~S53k#7QMvVymar1#JYGIS<WJ``@%5;3DIY(*8DBQaCdXt|@ zyGGMIk240i%b5ZW%&0o*xKb<C4ncavDsmYf;+iG7a2M1st3G~S40wRQ$O3^A;9a4V LDhd0doF?@K@FZkg diff --git a/src/test/resources/fuzztests/1538.html.gz b/src/test/resources/fuzztests/1538.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..77129a2a65bd99d1fcbd0bf6e9122b33ea2c6ab7 GIT binary patch literal 186 zcmV;r07d^FiwFp|C*@!O3o$h_I4)>(ZEOI2O*sz2Fc4e{ihP1Eq(_4#nn*l?!X_5B zWU?q;iZncc{EnI`bsj-%1*AxbYG-EWFu?+1xb8M^K|!<$iE0zKnOKPIFlZ#S^v!~@ zsD(|IQY<A>=<`f>_G`!>-1z2tlV#N!&OMK_4e*z<ia0Q%YE{b>t*GgvbeN`;&*pxD onJOReg8F6Yo9CYe4>0|kzzGPxAfi%XUzFoWZxnE8y&nMp05fG$^8f$< literal 0 HcmV?d00001 diff --git a/src/test/resources/fuzztests/1539.html b/src/test/resources/fuzztests/1539.html deleted file mode 100644 index 2dad90a933..0000000000 --- a/src/test/resources/fuzztests/1539.html +++ /dev/null @@ -1 +0,0 @@ -/U!<!!!!!???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \ No newline at end of file diff --git a/src/test/resources/fuzztests/1539.html.gz b/src/test/resources/fuzztests/1539.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..b3c7f9f72b264a033fbcf05e13190cbfa82ae4a7 GIT binary patch literal 73 zcmb2|=HNK2{WO7r+tAe5QZJ(<H;3WvK}KE%1s-Mt-QWhsNvn6%Yp3uDT=<rgm&Jez R8cuCzcgW|Mx6Ph`0RZ(w6Gs35 literal 0 HcmV?d00001 diff --git a/src/test/resources/fuzztests/1542.html b/src/test/resources/fuzztests/1542.html deleted file mode 100644 index 57cdc5b08b86e7cb39834cfdd54eea8bb20b31b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33506 zcmeI)-Aw{P7=>Xwum-QZw1JH!*n^42km!}$n6V`a5#uH&;J6Hi(H(fW;S&fUujhP! zvoMUS@%iy~sJ^=z-b%Oe{%&|3UY^Fwi~9GPUU@a#tlmp+n#Zm;;p!oO{}A~U0fPk= zA&`hJ{*%QQ{d|INk$+E+V1Y#lB%%u}LLd=cU=ad|=mLunNOYJkjHy`^V1Y$xWkeTr zK^OD;R370G9^sMNgBSTizK}2E3;Dv)@?jf`?UQ(QL>*B_)Dd+=9Z^SqdkUbYrix;L zMX95p3%a0-`A(BZc!WoI#8)OhJ8|=fc_ai9(FGPEkccj@2!TX&fkg-;qKo6On5I@o zi5Xs7uvqWE^?HVJ-U@0VmR^;BMcX7o7pneosy?T3YH3$Z7j!`v^Jf@5!XrG=gGbJL zGTpu&p&^U?tBrN9lS@Nt3qexzLxC!uil^fBQ1LjGQ<v-1)32t*o@?&G7KYW2ZI-0H z{=V=|Kh{DY3+X)PY*Bm;3&m}@#f>_tlR6DazKpHEaGAm>U8XQKqY8Z60&i^Xcx){W zY>X4fV1Y$xJfI7@po{sjkw<uhM|h-mwIW~07xG0_zF?I<v5MVB$GeT4Bl*cP+jo|O b@evkSl*%t%&;?yowPmc*)++k3j{C6>5wex* diff --git a/src/test/resources/fuzztests/1542.html.gz b/src/test/resources/fuzztests/1542.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..ff30bd3dcbf3e2a5d98603f023acd796ba4537eb GIT binary patch literal 305 zcmb2|=HRF{dz!$&ZD?v@q?b{Wo5S$-)<Mog4gw4g)*IO3?Q$P%V5t@m;e2sA+b8Fw zlSs>+Cbxshch5MTcz$DUYjaMY_SV(M|L*Fk(>;}{@v1EN+|!a3pKtz%sXY~1E_>$I z@lPL1c3;od00JlNd7{n}K`byxy1H}$@!*G>AlKyZ!fJWL1!Wh~yE?b|IwF9e7fAA9 z&SKtY>Rp}N{XsGhvuC*`%4Iia8uqKco6-aWN=uYP)Yc!$&zA1do+N?<ghN^mJqfkA jozYzAItiruk-q3cgG$k78_j0S*Lb)4p8x$vO$-bGpx23p literal 0 HcmV?d00001 diff --git a/src/test/resources/fuzztests/1543.html b/src/test/resources/fuzztests/1543.html deleted file mode 100644 index 9f889b0c6a4b51a21549db1a64a96a812ee5777e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 319 zcmaivK?=e!6hxbq8&BX$5VDh?P!}>}={?#eu`RVJ=@;z6JM=Ju*E5L~MG$vC^WV&$ z>@{x1-PzWhD04?mgXuX3WqG4PN<tf5kQ5RbKq%MGNszIYq$fNYIcPY}uO5ru1?b#` zz4ST38hD0Fl3?s<Xf%V!tr{@0R%x$Rt13JuHODxc#+MSq(4*XJcgxtK15o2<QjIhf d(nlwA*7{pG{4w~N?|(_icuZBU{i^s8`~b>qVXy!I diff --git a/src/test/resources/fuzztests/1543.html.gz b/src/test/resources/fuzztests/1543.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..ba0c12344d61c8fce7c895b2f6b7fa989807c046 GIT binary patch literal 203 zcmV;+05ty}iwFp2IObpg3o$h`GcIU!ZEOIAk1-CyFbqYTiG>p|At7Z$5lCe~Um3Ya z+a$DAX{yvnbl?s>3=-EPNz1@cvDx46f7@q`SMheXH8;xKRnuU4&Ouq;XpoZ7Mi(T7 zL<SJb{Zt7u){^vuM<WLf$NAF_qjv+kcVRAlPOt`^;gKX5dm0+eAabh)jI34KtJSUw z&8+4aS9AGQVi<aqhvR7*ujl~OSWK#srb2olS+mv?xco8v{3S6hQ<ZC%gFk7^T|7Sl F002UASGxcJ literal 0 HcmV?d00001 diff --git a/src/test/resources/fuzztests/1544.html b/src/test/resources/fuzztests/1544.html deleted file mode 100644 index 5837e8443a..0000000000 --- a/src/test/resources/fuzztests/1544.html +++ /dev/null @@ -1 +0,0 @@ -<i<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t==<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<m<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=e<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<|=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<magit=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<hgroup>=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=1<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t>>=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t=<t= \ No newline at end of file diff --git a/src/test/resources/fuzztests/1544.html.gz b/src/test/resources/fuzztests/1544.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..296f258d4f4ecd840e9a26c475d806c85818f690 GIT binary patch literal 366 zcmb2|=HN)Pc$UDxZD?v@qL)#Uo5S$-+(BNK00!5Kv(nDLKkBqWKq<{U)StCV!RDb# zdVRiWNH*^Q<|q6N#Dj$WhWnwaH(bm817un}nJx#@zs}t3uE2a44;S4a{P!|5LyYoW xc3di`FaB=H`)Mz&r+;C?ZuEz|`9Bzuq$UJ=)gbdBQV%T6B#T<6gz>X5005hkFlYb( literal 0 HcmV?d00001 diff --git a/src/test/resources/fuzztests/1569.html b/src/test/resources/fuzztests/1569.html deleted file mode 100644 index 0efe6370ce1acf628cd7bff84ecd0392e8eb99ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317288 zcmeI*J8vCFwgq4^zdvFiKn$Gpm?1@hFrX1c1!N!t4jc$HK!JgsSjmkX`1csF0_Jw^ zK>ja=W8JFi$EoV7?(-mpxduK49&vE^IK}SOd#}CL+9xL`-`srnpPTcG|NY_ni|+J3 z`?%>&yNj;7?QXi`?%&<`lAG>R_`h$@@6IoteEB5)GrS=D=lRmB(s%x{y9wVf{;>CF z=XZZxj6bLSLq2}k{E+?^OJDx4yB}}gJuiN0>vxN<3cq{$fzwx9U0xUO<jt$gmtVhq zefcK5>;9KW@8h`pDZHhdh4=h<-fj5#(~%zdZo@m+9M1f$e%pAfPtS%odKzAR)xYcR z_}Bc;9RK>a;~)Obs~2m>b$WGuIeeG!tAua4`EB|Or1h=CWfOkz{~R41r7zj%&$I2f zzKPe_qkQ}R?Od&%(L>)zH~cN0?Q?KH|Mltb<%9d}m*4+9{VLtT{dN7t@H0MsSovcg za%}Z=G#uFpY=JGS_sxJdux0I*d?1R!mZ7xC9<oIRp>9dF+yPaZx<%cRie+`nrhtbn zHQvG&JyG6KZ|S{LbEj^p?<3e!p9yS%EwCk$c=#4vE-trct$Ri`H76NWjLXf&C9NV1 zm)iv--DePy9cUtZ`$3L5(E=2k6Fp?$r6;QEe?3usCVC=0k)B9TMBg%0{44WSGw9SH zt<LG`TZ-$5zNH~3%Q|j7(V)A6EwE*h&f;6>a!YE7E;qu%K5QE<cXzckE*F<u+8b#8 z*DVaw@XVYjE8*2G>Xz~$t!`1b9P<+mhDct;@VK1Y{ko#6pC;o8NsE`y8vxh>TVTs* zwuUVQSWI5?tUQG+Ywqns480kXk8i=ZtUSqOEKKaax<%chZc(=+>0OVDdZOicovg5E zrp~Ba<hR_1+CK>C=Tw58NKdpH-341Z*-#;!?f?}$Y=JGXrGMf!vH%k^1Y2Os_5@Ga zP_m(9L&Zp4xpR-54xAq;zNP2fS}LvfAP3)4Of-grlb3ssZ<)b0Y=JFB-uz;Sxt>T* zq$f(x)g{_M&j0n5{FXYaM1D(sCI>LrdLn!azGYUx!xq@muXB<+CwFdc@06Fj5YpIE z*WaGYv|KnY*aBN%%VtsFs(=Ev$cD<3N#xGO61_FO%TKA#FAUl&nU*pwhs!{wWzwm| z6|#x=$Yj663BeZF67%@ww_F#J2VTX96~&RX9>8t+3~b3t_|~y4!ZE&Ofk4O(G}8;> zTNaKB--2(!x8Pe6K+3d~X*tBKr)TG|{_W<w_h(&f4du5Kb?#JO=!wb(Ku@G6lHVe~ zh322;pXNWP`Dy29=f=Ux_2rN;*^{61V5>!2%pTQun{4G(EUI^2#f$slBV380|4$RZ zDTDE%liw1~Wqy0k4{5Lp$l`8Ul@1^d+R$ZAl#MmaiSRA>7TEIppQm39`p1Zxc8J&^ z@<a4&bvNpkRoQeVZqpO#iQ?^Y@=bi6Y)>7~6TueP(l^1ajxUah<+sRh*^Z<#7Gx|0 z(($Hy7UaWomTi#)hb^!LwxpVU>G5k$BpYg!p5zZ%GiWntGw2AN`$<{n6DhfK^?d|e z>NAl$ml~X%OM7*Tx<%d6$%cA;$*cGonmct%{ft(()Muh@QMZI9X%d3A<Ys!JFa~W% zX7MfI%DC;GZ?2xnBYAs%7;~K;DL)ZE5kC<>QSMQXnN+Z4BzE^0rQ%!gE%+9E%UgU) zTLo-R)JNnNnV@p#7Rbr0W6pY>Se(R(qn>Cuh|R^s2fcfo621lBQq^j(WdVw1TJ96o z23ueYY`N_QQt8dB7waLA>K1iNk>GQsCt7$A>xl+`K5R*zYdb`u+@dv5m-FB4xkUoD zz?S^3fi1AbexfH8jLPDc#Vs3ZBsk=^$ZwI~BERKxOl4sUY=JGX1-5jRX#dL;O1W#w zUiIZNEt{<2e01B3YuaiydAag(+k<X(i@HVKqHa;Qs9S>Ko4$p<g}$XHj9a7ntmrm_ zHiI^U-bJ{PpVGzx5*E+X&e6_=%V34Mpq-<gTMeUuEtTj)-LeTpfGw5vasu2ye1)g? z)hhn>^{$>sPoyUb6xq|W`gzlwxTsrTi@IgXLa?*-x>&VGmrIvRmrIu`({iV>GSw0* zEYS^p3w;ZH%T#V{A~9*_Xy={`SwZ$Hh7SHQ)2V6aXy<6>l5z~Tz!q8D#gxGAvxx-X z4wCj0)w_7?C#uiHej@vc>?ew2d)hhLxk<`l23_b%i%;lVWJAe@k_{Dk3UeZJB6A{h zqNAhKMirWzEwX7d=<esgKK;FXaKHTmTVM-pfh~hZ`dOeUb|%4L%V8Um&7jSow~tq7 z=hQ7fe1FmHm&68JU<+)aZ%HRW-$LI)-*RYkN8eIwIp|yJGof#xZ=r9YZ~6M|>r46; z`j*pi&vzkdDL$cZp>LsYp>LsYdHdQ7`u^Sw+6?+on!=pOoXDK$e)wo3sr^K>bD=A{ z>F8U!*mtC`5nL`VcTC;2LquL~F)8F##N|dxf2F&aq5@vLT%o@0C+dEJEwE*$&_w%* z>?g|8%2)AM&VOk3H_xcy6B!F;(CQX5=;1+fh<!Xq;9_hKTVM-pfi19Q?h)+`7u`S! zOv79|M6Qdx0y{+P5V1qV4iP&<cA~?w?!K9pcSv!cZ3EA~{XibzTj+A>a_MsEa_MrT zpscMXld%xaWuFePczIy#^+eyjKkK@{Egn7@E{I8Wi@HVKqHa;Qs9V%63$k8&7422D zS8+Un@Tjm?akF%Fzmz?GB7P!%q7Y7*-w6Cf3xx-4nUh6}t4AM9h?o-<<l3+xLB;|u zH+sZ#xHf|}gT8(DeDl;^1Z6q@a{kZn{<s(-78a&rutiU#Cz?HEH|UCQ8FMG(<tCue z6HNy4?F$QJao5}eY^l!#w!juy+_Jb=0!d^n$XLi7zO-}kG1}Uso1ao4H{^rngXV+g zgXV*tH}O$`8Fbxz(BT*|ivZ1k$E#@9{KDjfpQr$f5KkP?V+~1*m%|p=Lf=B)Lf`UG zGa>C<QN7d7ZBcxvTZ%$N-4ZAKv2-iQ!u}K30$Xmv?Q-(XP4K>NSChdO*s@&~0$c1p zv-^xLcTu{|Pjr1nJ6G$(Xy@uPp`D|h%cqwuNyxmpgDv%a1Y7Defi18Fw$SC$<px?O zUIr`7#bk@IS23=qZ8OIYOwUxe)X!*jOMNEl7IlldMctxqQMbhO9Gd^}2;vxb&mxSU zsGEybw{UDNFTw5kD6@Fn1W)-bsnlu7G%gqHUfVi3ntz&qn*UiOPHL%SaW@^e^YO|G zO9WeB3v7WcM@P#NFm=nKFi57QOv~*mWId6dD0JI5-LufR&%OSzCAdB85V1ofPHB63 zc;{P$E|)HMTmzp!WX+(>phq~lz8q9Cuw`#jn!csJk6=rECiE@zE%Yt)E%Yt)E%YsE zLDnGgp>N4H#kO=PcR${~3u2%Y>Ja~(Hc9<I*0-F`svjzvMOBN=>+mQoo!9I>3v(u& zSA8ZrFP)dpOXsEY(s}8;bY8KKnvYM{GO+(aomW2KqyDvZ+SX}3(R@P+70o#I-a}|c zMMFhHMbmLazFt=P<8pDig^udV_Qr+5M{CLjTVRU>syRt1$w87si3?B9>T9BT-$=Jh zsS&FQfV~Y|-J)*Ui7ZsN4537@1-4Afms`<F^)qT1KI)0|L{fRB+ex=GgAVHp=hLaA zbuI?<bD{IMuj*}OdZL$e;-DRHH9Um%L{TQHZuzG<3+P*<#7K#mMX}NR)BKN;7BgsE zE-n|B8}!V2B0W*wU2|Jc-sy`Z_ow*}r8K_f*T=a@@GT*rz^0N-)<J7ljBmLv0@iT3 zag1CXNsE+(EwH6vd0>mWMctC!1p{jSW>bkxB{O=Q4|*p$%!akjrV^V<Y${3JNUONm za|da=X=yqY4j~TO-=4muE{Y1rDqSu=5kJwEn*_GtTktJ~ss`WEDv1slc)%9e0$X4U zz6IZMj&H%Y;9K674e7i>lJj3YEmMADQ9;-YE7Ni`0LZkQJf*uRX<!R%Ip1jZ9urA< z6?qkT6=z}$bxV!6_7mApWIvJpL@Nm%*}O9CZ}}8U7IjOZY$#|0K4?B@KIol<pRh%y zWv&X74HZlD7I~DP(sBNYOiP)TGA(6V%CwYeDbsQ@iW(|#n*Wx=n^*CkS8+y<^D6Gt zveM4c&e6`D(azD%(awc|$bI$^9H3!1vXJdYJ2$DLvN_UWDrEW=`WE_@SpiSqLf<m- zA7;>I&}Ps(HS}iCX3+US)ePDUI%?eH<(A>F@^a<n9)B?)<;M@b2<6enYdHebx129Q za3(ZWx2Rk4yGGrzbkpRg1Y2N>{X~s5w%L;<8*$6D%pS9Jxq2c!k=(i8|2+L_l-q`2 zVm(nRDK5H?AG+QgI1Y7BL)>0>N|#HQJI<xnoeHwJ?GT~MosISBa(5czWlhVYk&=2M zdAWtECTj*K4V#`wPoyW(6Sc*^dZIl$z~bfe1*K-tX3+SSz8=Q6?9DF0mcE(nA63D? zq;65S^cTb2w4`n+z#>H6g_y+oAq_Sp`-$u)%9sKBpf5&GBy5>zqRpCs{ik9D6zPf7 zE%ICBxBOsEbaZsY2h9gPyAg_s&0}uW&=VDfh@L3idZ{feMCFfcj4^{gsNAFZw`;y! z?f3Moz9yPVk$;bRlNT>n)*_r<$ykuFAY);?SuWRZmW&<C2$AvedD}f^l_ncXPoyW( z6O9ua*fQ%;)Gg{3b&I+s0HmI1t!m9I1%ddAO9Evq$XJjK^`xSf=yK_D>2m3Ec@=jm zE3?9)$w1TP@<H=K&nPU|0$Xn1J>RH1j$5kZkjG*_k^MyW6ZKw&rCa2u!zobumSxc) zT`pZNT`p|7>ISXRv%nh9^Uu^R;o0<<Q(3xPy4+Ry5xzxFq$ko7>4{ba&0)Z;Zixf# zOrsj2Y&VRPx+O|>4@6SBT)JGkT-cHx!jFukum!fj7T5w?x&Vczu}X_^RU2P2&Hv$~ zw4O*$bUxlvkIP)EThuM;7IlldMcp#0$7cncx`kJ9C*h}z1^b}$gd1K(UPWF-Ud32j z%K4Y`-}8d0(>U&ij59k#!o9KIhT7ugvbbRj%|FfmTr$L~xKqnYmrIv>pqh;4zbHgx zagPJK&CJ`b`M*B>&8rxn&0nT6|Kyw42cO^lVLwsU0h&RZL7PFFL7PFFL7PFpN#37c zL77j}in8+LB$g8Yxj7$%apStf_`hwMHGjzR6V-acM40SXrR7J8Z^5_7hT>J6##$9P zz?Pk3NMu9tL1%?v)Q}|4uewFu@>608>^|!r0FwMf^?d|e>NC+3C1O_HGA*Rs9&!a+ z>iY<`)Mo-)U`ud_Ph-G0uV710BI0rf+B>^CvOKI&X2Az7V<8)r^kEacioA+D4I*F* zuVOx3<yDL&daDS_Pst2A4cuGDl!YC^7TA*CHLwM?@ImuI^FhCT&8xU1(BmiKC)!Sd z_7%9DtubA6&K<)R*un>WI0Xt@colgS$MM^C8$~~^li#w14wJDUV?oBk4zBOYGH?o8 zV9TI)u(LJLXLh#QeKr~Z_E>BD0I9s5NKd3E(i1H{eqjr*BClfbXVT^7$sL&-h*yzU zaY_OVtYV04zLMXvaI<7nJ(~Zd$b9i~b!ED}iiZ!pcol139<O43CTY+kzl9H)4_fYA z<vB}%B<dELf13aKHk#J-E<YuHB7UOV?&(>5O*9p*)8ki96sKU#Gvb3DC>GcPTVTr$ zsVc>d!mG%u$g9Y!$g9Y!7*!u_6+EwEIF~scn%0+0p0sItb@$`#yI@w!UOw-Vzim9E z^+fAqulYb#-J)($x2RjxE$S9^%fbkopQxE|R%rF6!lKQt#{HdvczuYX4c`(aaIFA` z%Ul#z&jbcymv$~crtIQ1gEoWC?;1Uko=8umC(;w?iS$HzqK7vvdZJd_B6rT5NbcO> zHk_Uifi1A*CQv+avHklU(vr-ei=pzc45+w<%%F#DIIyL-E(Te?x<%chZc(>f@j=(* z03URHCVbF#wl;G2i?MAsk4#A?u%*6_U`u@_um!fj7THiMVMp{W^eyx)v~xoYK*?_Q z+SZfkws{`wt6h#Mt5snOY=JGi+%)+q6;fupT)NzK^}PI+M2XPl#`a$}RB{HZTPDji zAM{Sfn5=3|a8f*kVaw;mmegDHT8qV%5I_I*>2F(8Y)y%p>-Z6LIx(%SkYG&B>t>-7 zcV@FDXu};vPjNX>^j!4!+S1)dEl4eRxnraZY=JG5$V*6SOKlp|w{DSDAq<y;o=8u$ zUZuen*pk+1JPVVkdA=gPB~S=9U~dQ$8<}>#MQo|HrPh{OTWa6FUhYwBl04iB#^q2e z`neqHGvRXJa^P|Z&7BR{OAlg7WJ=^*V@uKVc@;hBk(?8c>K1iNT(1}g=2v<mJ<*UM z6fXyL%QAtWZc(?WTR5n=Gh^kjnas?u2rdUMhvP2>r2P2dk!3+S0vE;*?o94X?#yV! zh|0aV!D(pmrb8JDe0&SO#nzNMKttW4Zc(?4A{u_8qREjyzEW856Sd3|(#Mw^Hn0V@ z@GACx`CoH7G3(xBEZm*<W7_>x@UR88z!uojvtay0p>a7s3`ujzP_H5CqG^0H_|X&T ziM~%cKkAl7+>yFPHq=nU1}M#L5ne@J#gS&sAF_10bh+cumM)hrmoB$Q$yShZrf{Ik zrOVC5F7z$*E%Yt)Es+6?o7R&S?(k}&5YoXG*aBN%3v7A&n!bg;CFXtR6qY&BPAzM; z)`WJBc1{-e5|Y9edAZ5PBrms%eMi!Am=l>3nG=mJ$HSQ&nU?i^1Y7Dek!dN@Ql_PB zsIO&1^$}q!ksdZ+CvL}_$ehTW$ehTW$ed_d1<$L<PsFR3oO*Vjg=4V$j8}0acK28h z$WO#i#7|`R*)lyP=f8UZNSYJXGZAy5`b^A;%!$m2%!$m20<9A-19PIpZOGz2z=oAO zItvSyoPRn0uqBU^Ndnmna0m2V#oyjPPdg`LVJE|ij0G7B`4bto(6`XH(6`XH(6`XH zEKtFx@^ZICcV$frA2c5{A2c6ypCBPGcecUN47z^EnnBlRVg_vnZ3b-yZ3b-y9h14t ziN+%^gMKout0_9oDZuUS$J=+|uu>Bi|NT7HR{=?jS8r+P&56v3%!zinY4TH|Z%KP{ zV9Un*LpwKYmn-g#;uAAyGiWpDx<vc-RqZK$dR9MgniCiKE%IBAyXh*ZucZ1q8X9f7 zT)JGkTzm_@WuFbn_?GEuqi%_%$3ARZCZ8D|!g?Y-k)B9TM3+mKOP9MKkD<%`ehbx} z6&6jF$X>-?e*g3ItFhGyTD>m@UgYD4o}+%WW5c(YLBGDtsU=+Q%N2D~23>lhOpeh) zeAoh8V9S=9q)$qav9RYFZP)@^U<+&sW&}Oaht2ZF-ru&jGuWKSoG9kV9iG@kJGaw# zP2HkyQMagD)GZgGa8tLO?xW@jjN|6zb1?^f%T5nqd`oB!zI%U$Z;4W<C}aqA?;uF# zRa~%T!WP(K20bo8QnwU^$l#<~LDJ&o>Xw5Z!0MKRjt$>}Z^5@@HEv6&0b5{8k#?6| mFM1+9QK;YgY@Y*&1K0vvU`t^YNJ&_FqFEF#Z27Ni{{Ig!=W~Ss diff --git a/src/test/resources/fuzztests/1569.html.gz b/src/test/resources/fuzztests/1569.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e97c31f3596b3de09fecb10f6341843980ccb403 GIT binary patch literal 2006 zcmb2|=HOU$>UjbKx1p(-rCvr!ZVtoSD|@{T2Z%5{DEqFsB!O-EYDrU74y|hoWkqCU zRGgMJ>+Q0z{9)6?_imT<m2$y!-`g6wx!Om&y5id=w0tf;&sh^F@;0?yI&#vy`7+BD z_r2Y{Nub@NCGCWhrB3Vi2|9b8%%AP4?Wuiw>bYdO3HMh$UbIVon)9^luT89@J)`(v zK2du+d*PQIv+eF%<;sP!@9MLPR$q0Z<DT)Wt;Za`oD0h>{+_UV+hyPG`&!cuyZ)Qe zJM(AtvcD7T{%&D@czE%<eu>h=r4lCR7Qf!R()s7E{T2EjYGYqdz9xEIdTZ6r^-n%L zbf5a}>G`Q%OY=^d?0>#FIDPKf^Gj+z8tuF-Zr%DQwRG0eSi8M8{qM`mOaDu(|9Vtk z%=VVKaop`2Q(bcsxub6!65F<+Rd<v*8V*Az5!9{MWl@$m$nmY5HF-1%kLKS2DaZco j(fyyFH0#ooWhSYkiIB>P@M-_8Ph8BqR@!G=XJG&U3!t6# literal 0 HcmV?d00001 From 68890368513e30e3aeba9ec0ef4dbe8e1c173707 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 14:03:29 +1000 Subject: [PATCH 547/774] When a tag has null chars, consume them all in one hit vs stepping Fixes #1580 fuzz timeout --- CHANGES | 4 ++++ .../java/org/jsoup/parser/CharacterReader.java | 5 ++--- src/main/java/org/jsoup/parser/Token.java | 2 ++ .../java/org/jsoup/integration/FuzzFixesTest.java | 9 +++++++++ src/test/resources/fuzztests/1580.html.gz | Bin 0 -> 817 bytes 5 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/fuzztests/1580.html.gz diff --git a/CHANGES b/CHANGES index 9ff4ae7f53..cdd988280b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ jsoup changelog +*** Release 1.14.2 [PENDING] + * Bugfix [Fuzz]: fixed a slow parse when a tag has thousands of null characters in it. + <https://github.com/jhy/jsoup/issues/1580> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index a781a0c2d9..35f52fa73e 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -362,8 +362,8 @@ String consumeRawData() { } String consumeTagName() { - // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar - // NOTE: out of spec, added '<' to fix common author bugs + // '\t', '\n', '\r', '\f', ' ', '/', '>' + // NOTE: out of spec, added '<' to fix common author bugs; does not stop and append on nullChar but eats bufferUp(); int pos = bufPos; final int start = pos; @@ -380,7 +380,6 @@ String consumeTagName() { case '/': case '>': case '<': - case TokeniserState.nullChar: break OUTER; } pos++; diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 78318d8d48..9ade8e0d0e 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -165,6 +165,8 @@ final boolean isSelfClosing() { // these appenders are rarely hit in not null state-- caused by null chars. final void appendTagName(String append) { + // might have null chars - need to replace with null replacement character + append = append.replace(TokeniserState.nullChar, Tokeniser.replacementChar); tagName = tagName == null ? append : tagName.concat(append); normalName = lowerCase(tagName); } diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index b85bb17672..b9b056588f 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -69,4 +69,13 @@ public void parseTimeout() throws IOException { Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } + + @Test + public void parseTimeout1580() throws IOException { + // https://github.com/jhy/jsoup/issues/1580 + // a shedload of NULLs in append tagname so was spinning in there. Fixed to eat and replace all the chars in one hit + File in = ParseTest.getFile("/fuzztests/1580.html.gz"); + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + } } diff --git a/src/test/resources/fuzztests/1580.html.gz b/src/test/resources/fuzztests/1580.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..15186dc13cca4481151b2d2f7b81f756dff5ea64 GIT binary patch literal 817 zcmb2|=HOVG@+yIW+tAd)Krf>tH;3WvxrMxk9RwH-E@=(;E@1wl>VY4RlI@P<@O?|S zdmC&JF*-ag*DZB*ZTJ14#cLOTVr3YN;K1U(dj7%auu(S+jS$%Gn*Vx*rTzQL&VPHg P7SE4(v1$9pcy0y&%rYy^ literal 0 HcmV?d00001 From 478b568061b317c79ba6fd018d9ac5fa931bdb02 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 14:21:34 +1000 Subject: [PATCH 548/774] Rangecheck bookmarks Fixes #1576 --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 13 +++++++++---- .../java/org/jsoup/integration/FuzzFixesTest.java | 11 +++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index cdd988280b..72874c7dc6 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ jsoup changelog * Bugfix [Fuzz]: fixed a slow parse when a tag has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> + * Bugfix [Fuzz]: the adoption agency algorithm can have an incorrect bookmark position + <https://github.com/jhy/jsoup/issues/1576> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index dcfef80283..82f642bfcd 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -631,13 +631,18 @@ Element removeLastFormattingElement() { // active formatting elements void pushActiveFormattingElements(Element in) { - this.checkActiveFormattingElements(in); + checkActiveFormattingElements(in); formattingElements.add(in); } - void pushWithBookmark(Element in,int bookmark){ - this.checkActiveFormattingElements(in); - formattingElements.add(bookmark, in); + void pushWithBookmark(Element in, int bookmark){ + checkActiveFormattingElements(in); + // catch any range errors and assume bookmark is incorrect - saves a redundant range check. + try { + formattingElements.add(bookmark, in); + } catch (IndexOutOfBoundsException e) { + formattingElements.add(in); + } } void checkActiveFormattingElements(Element in){ diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index b9b056588f..2abb11decf 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -78,4 +78,15 @@ public void parseTimeout1580() throws IOException { Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); } + + @Test + public void bookmark() { + // https://github.com/jhy/jsoup/issues/1576 + String html = "<?a<U<P<A "; + Document doc = Jsoup.parse(html); + assertNotNull(doc); + + Document xmlDoc = Parser.xmlParser().parseInput(html, ""); + assertNotNull(xmlDoc); + } } From 9031164b7f2acfd99391a7149d4f865b6b79e9d7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 14:54:29 +1000 Subject: [PATCH 549/774] Make sure null elements aren't added to the stack Fixes #1579 --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 1 + .../java/org/jsoup/parser/HtmlTreeBuilderState.java | 8 +++++--- .../java/org/jsoup/integration/FuzzFixesTest.java | 11 +++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 72874c7dc6..d77efc0d17 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,9 @@ jsoup changelog * Bugfix [Fuzz]: the adoption agency algorithm can have an incorrect bookmark position <https://github.com/jhy/jsoup/issues/1576> + * Bugfiz [Fuzz]: malformed HTML could result in null elements on stack + <https://github.com/jhy/jsoup/issues/1579> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 82f642bfcd..835fa323f0 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -356,6 +356,7 @@ boolean removeFromStack(Element el) { return false; } + @Nullable Element popStackToClose(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element el = stack.get(pos); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 518793c246..3d5e1d45e8 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -8,7 +8,6 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; -import javax.annotation.Nullable; import java.util.ArrayList; import static org.jsoup.internal.StringUtil.inSorted; @@ -1530,8 +1529,11 @@ boolean process(Token t, HtmlTreeBuilder tb) { // that space into body if other tags get re-added. but that's overkill for now Element html = tb.popStackToClose("html"); tb.insert(t.asCharacter()); - tb.stack.add(html); - tb.stack.add(html.selectFirst("body")); + if (html != null) { + tb.stack.add(html); + Element body = html.selectFirst("body"); + if (body != null) tb.stack.add(body); + } }else if (t.isEOF()) { // nice work chuck } else { diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 2abb11decf..dbe03c6601 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -89,4 +89,15 @@ public void bookmark() { Document xmlDoc = Parser.xmlParser().parseInput(html, ""); assertNotNull(xmlDoc); } + + @Test + public void scope1579() { + // https://github.com/jhy/jsoup/issues/1579 + String html = "<table<html\u001D<ÛÛ<tr><body\u001D<b:<select<m<input></html> </html>"; + Document doc = Jsoup.parse(html); + assertNotNull(doc); + + Document xmlDoc = Parser.xmlParser().parseInput(html, ""); + assertNotNull(xmlDoc); + } } From 2b8253d100a401963fec81ebe734513858506e30 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 15:01:34 +1000 Subject: [PATCH 550/774] Test for fuzz overflow in #1577 Doesn't repro for me - fixed by another commit? --- .../java/org/jsoup/integration/FuzzFixesTest.java | 12 ++++++++++++ src/test/resources/fuzztests/1577.html.gz | Bin 0 -> 771 bytes 2 files changed, 12 insertions(+) create mode 100644 src/test/resources/fuzztests/1577.html.gz diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index dbe03c6601..db45b41965 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -100,4 +100,16 @@ public void scope1579() { Document xmlDoc = Parser.xmlParser().parseInput(html, ""); assertNotNull(xmlDoc); } + + @Test + public void overflow1577() throws IOException { + // https://github.com/jhy/jsoup/issues/1577 + // no repro - fixed elsewhere? + File in = ParseTest.getFile("/fuzztests/1577.html.gz"); + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1577.html.gz b/src/test/resources/fuzztests/1577.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..953067b1b6070656eacdaf9663d94152eaf5be06 GIT binary patch literal 771 zcmb2|=HU2V`6_{d+tAe9TrZ;}H;3Wv72mwe4k8Q>N|=L|ZhGA$I#KChQ>$Q7;K2u* zSe`PhxqK~8Np+j3HMa$;xkG_4-v#9_5^KEwNxjk%oY)}t>b>Q*^td-Of6miwUsY~$ zX7gf|v&T<<Tr#V4vW1TS!<VUFR$nyW%U^xLze@eIb4TB?+h6qa)c79n-E3g%^Zjmt zd1PutZhQExt-7zKJPiK%VMlmKv(4)N;rfSDO{Y!Q`(+&{qxLxKp4PmY7}e~~-|wfb zw3rgjv#qSBwDZ>7{d<006Oz`lzaQ3nJLV0qpZW29@xiU;mutUPy)nCR?#v#=k6Sx4 z(r5{uMVma2G<;K=F>Pn(`I&I=vv1~nbHTG`2!lV>oogDOsoA9?yYiWRu3h|*A1adW zYa{pYWm<RD%Y9pSv}VfXLOabze+78IImhhdaecm^_`|EhdAy&EEZ>P<O__5>Xm0bh zs~u6+Yp?CS$)mV;$I8lOAOAX@xKwk;`%;;uRLarqSynF>$$fk(wd-!4b=SGq8`tTa zzqY<yU*`GYqgB&8%R1KGsoZ9E?epn-D{h#i%sTe=XRNh%PW=}7W&3Ts*>5hG^Yz)) zS@YA+=IL%T&Qtgo?)qm_E~9<>!v$;DZf3@4KiL11Nhe!wQ{0RF9pXJ-7Int!A^Qr6 f{cL|~-4z!TV>*E1%V(|&+cSKe(|6QbkC6cYnO|vB literal 0 HcmV?d00001 From 17d56d13afa74c2786bd3fac6224a270c8add4b0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 15:19:40 +1000 Subject: [PATCH 551/774] Suppress incorrect super.clone warning --- src/main/java/org/jsoup/nodes/Node.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 0efe532da2..dd0a190c9e 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -708,6 +708,7 @@ public boolean hasSameValue(@Nullable Object o) { * @return a stand-alone cloned node, including clones of any children * @see #shallowClone() */ + @SuppressWarnings("MethodDoesntCallSuperMethod") // because it does call super.clone in doClone - analysis just isn't following @Override public Node clone() { Node thisClone = doClone(null); // splits for orphan From e62a447c85a65f67f311a82bddd320e0d5a1301c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 15:59:24 +1000 Subject: [PATCH 552/774] Wrap stream in a finally -> close to ensure closed on error Big diff but only change is wrapping in try {} finally {} --- CHANGES | 2 + src/main/java/org/jsoup/helper/DataUtil.java | 166 ++++++++++--------- 2 files changed, 92 insertions(+), 76 deletions(-) diff --git a/CHANGES b/CHANGES index d77efc0d17..ed79791e5a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ jsoup changelog *** Release 1.14.2 [PENDING] + * Bugfix: corrected a potential case of the parser input stream not being closed immediately on a read exception. + * Bugfix [Fuzz]: fixed a slow parse when a tag has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 406ea5bdfc..a111a4e6ef 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -65,8 +65,13 @@ public static Document load(File in, @Nullable String charsetName, String baseUr String name = Normalizer.lowerCase(in.getName()); if (name.endsWith(".gz") || name.endsWith(".z")) { // unfortunately file input streams don't support marks (why not?), so we will close and reopen after read - boolean zipped = (stream.read() == 0x1f && stream.read() == 0x8b); // gzip magic bytes - stream.close(); + boolean zipped; + try { + zipped = (stream.read() == 0x1f && stream.read() == 0x8b); // gzip magic bytes + } finally { + stream.close(); + + } stream = zipped ? new GZIPInputStream(new FileInputStream(in)) : new FileInputStream(in); } return parseInputStream(stream, charsetName, baseUri, Parser.htmlParser()); @@ -74,7 +79,7 @@ public static Document load(File in, @Nullable String charsetName, String baseUr /** * Parses a Document from an input steam. - * @param in input stream to parse. You will need to close it. + * @param in input stream to parse. The stream will be closed after reading. * @param charsetName character set of input * @param baseUri base URI of document, to resolve relative links against * @return Document @@ -86,7 +91,7 @@ public static Document load(InputStream in, String charsetName, String baseUri) /** * Parses a Document from an input steam, using the provided Parser. - * @param in input stream to parse. You will need to close it. + * @param in input stream to parse. The stream will be closed after reading. * @param charsetName character set of input * @param baseUri base URI of document, to resolve relative links against * @param parser alternate {@link Parser#xmlParser() parser} to use. @@ -119,88 +124,97 @@ static Document parseInputStream(@Nullable InputStream input, @Nullable String c @Nullable Document doc = null; // read the start of the stream and look for a BOM or meta charset - input.mark(bufferSize); - ByteBuffer firstBytes = readToByteBuffer(input, firstReadBufferSize - 1); // -1 because we read one more to see if completed. First read is < buffer size, so can't be invalid. - boolean fullyRead = (input.read() == -1); - input.reset(); + try { + input.mark(bufferSize); + ByteBuffer firstBytes = readToByteBuffer(input, firstReadBufferSize - 1); // -1 because we read one more to see if completed. First read is < buffer size, so can't be invalid. + boolean fullyRead = (input.read() == -1); + input.reset(); - // look for BOM - overrides any other header or input - BomCharset bomCharset = detectCharsetFromBom(firstBytes); - if (bomCharset != null) - charsetName = bomCharset.charset; + // look for BOM - overrides any other header or input + BomCharset bomCharset = detectCharsetFromBom(firstBytes); + if (bomCharset != null) + charsetName = bomCharset.charset; - if (charsetName == null) { // determine from meta. safe first parse as UTF-8 - try { - CharBuffer defaultDecoded = UTF_8.decode(firstBytes); - if (defaultDecoded.hasArray()) - doc = parser.parseInput(new CharArrayReader(defaultDecoded.array(), defaultDecoded.arrayOffset(), defaultDecoded.limit()), baseUri); - else - doc = parser.parseInput(defaultDecoded.toString(), baseUri); - } catch (UncheckedIOException e) { - throw e.ioException(); - } + if (charsetName == null) { // determine from meta. safe first parse as UTF-8 + try { + CharBuffer defaultDecoded = UTF_8.decode(firstBytes); + if (defaultDecoded.hasArray()) + doc = parser.parseInput(new CharArrayReader(defaultDecoded.array(), defaultDecoded.arrayOffset(), defaultDecoded.limit()), baseUri); + else + doc = parser.parseInput(defaultDecoded.toString(), baseUri); + } catch (UncheckedIOException e) { + throw e.ioException(); + } - // look for <meta http-equiv="Content-Type" content="text/html;charset=gb2312"> or HTML5 <meta charset="gb2312"> - Elements metaElements = doc.select("meta[http-equiv=content-type], meta[charset]"); - String foundCharset = null; // if not found, will keep utf-8 as best attempt - for (Element meta : metaElements) { - if (meta.hasAttr("http-equiv")) - foundCharset = getCharsetFromContentType(meta.attr("content")); - if (foundCharset == null && meta.hasAttr("charset")) - foundCharset = meta.attr("charset"); - if (foundCharset != null) - break; - } + // look for <meta http-equiv="Content-Type" content="text/html;charset=gb2312"> or HTML5 <meta charset="gb2312"> + Elements metaElements = doc.select("meta[http-equiv=content-type], meta[charset]"); + String foundCharset = null; // if not found, will keep utf-8 as best attempt + for (Element meta : metaElements) { + if (meta.hasAttr("http-equiv")) + foundCharset = getCharsetFromContentType(meta.attr("content")); + if (foundCharset == null && meta.hasAttr("charset")) + foundCharset = meta.attr("charset"); + if (foundCharset != null) + break; + } - // look for <?xml encoding='ISO-8859-1'?> - if (foundCharset == null && doc.childNodeSize() > 0) { - Node first = doc.childNode(0); - XmlDeclaration decl = null; - if (first instanceof XmlDeclaration) - decl = (XmlDeclaration) first; - else if (first instanceof Comment) { - Comment comment = (Comment) first; - if (comment.isXmlDeclaration()) - decl = comment.asXmlDeclaration(); + // look for <?xml encoding='ISO-8859-1'?> + if (foundCharset == null && doc.childNodeSize() > 0) { + Node first = doc.childNode(0); + XmlDeclaration decl = null; + if (first instanceof XmlDeclaration) + decl = (XmlDeclaration) first; + else if (first instanceof Comment) { + Comment comment = (Comment) first; + if (comment.isXmlDeclaration()) + decl = comment.asXmlDeclaration(); + } + if (decl != null) { + if (decl.name().equalsIgnoreCase("xml")) + foundCharset = decl.attr("encoding"); + } } - if (decl != null) { - if (decl.name().equalsIgnoreCase("xml")) - foundCharset = decl.attr("encoding"); + foundCharset = validateCharset(foundCharset); + if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharsetName)) { // need to re-decode. (case insensitive check here to match how validate works) + foundCharset = foundCharset.trim().replaceAll("[\"']", ""); + charsetName = foundCharset; + doc = null; + } else if (!fullyRead) { + doc = null; } + } else { // specified by content type header (or by user on file load) + Validate.notEmpty(charsetName, "Must set charset arg to character set of file to parse. Set to null to attempt to detect from HTML"); } - foundCharset = validateCharset(foundCharset); - if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharsetName)) { // need to re-decode. (case insensitive check here to match how validate works) - foundCharset = foundCharset.trim().replaceAll("[\"']", ""); - charsetName = foundCharset; - doc = null; - } else if (!fullyRead) { - doc = null; + if (doc == null) { + if (charsetName == null) + charsetName = defaultCharsetName; + BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); // Android level does not allow us try-with-resources + try { + if (bomCharset != null && bomCharset.offset) { // creating the buffered reader ignores the input pos, so must skip here + long skipped = reader.skip(1); + Validate.isTrue(skipped == 1); // WTF if this fails. + } + try { + doc = parser.parseInput(reader, baseUri); + } catch (UncheckedIOException e) { + // io exception when parsing (not seen before because reading the stream as we go) + throw e.ioException(); + } + Charset charset = charsetName.equals(defaultCharsetName) ? UTF_8 : Charset.forName(charsetName); + doc.outputSettings().charset(charset); + if (!charset.canEncode()) { + // some charsets can read but not encode; switch to an encodable charset and update the meta el + doc.charset(UTF_8); + } + } + finally { + reader.close(); + } } - } else { // specified by content type header (or by user on file load) - Validate.notEmpty(charsetName, "Must set charset arg to character set of file to parse. Set to null to attempt to detect from HTML"); } - if (doc == null) { - if (charsetName == null) - charsetName = defaultCharsetName; - BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); - if (bomCharset != null && bomCharset.offset) { // creating the buffered reader ignores the input pos, so must skip here - long skipped = reader.skip(1); - Validate.isTrue(skipped == 1); // WTF if this fails. - } - try { - doc = parser.parseInput(reader, baseUri); - } catch (UncheckedIOException e) { - // io exception when parsing (not seen before because reading the stream as we go) - throw e.ioException(); - } - Charset charset = charsetName.equals(defaultCharsetName) ? UTF_8 : Charset.forName(charsetName); - doc.outputSettings().charset(charset); - if (!charset.canEncode()) { - // some charsets can read but not encode; switch to an encodable charset and update the meta el - doc.charset(UTF_8); - } + finally { + input.close(); } - input.close(); return doc; } From 78f42893727546ef1d2843ca581c322292659f1c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 16:17:37 +1000 Subject: [PATCH 553/774] Make sure POSTS that fail are immediately cleaned up --- CHANGES | 4 +++- src/main/java/org/jsoup/helper/HttpConnection.java | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index ed79791e5a..02bd724a30 100644 --- a/CHANGES +++ b/CHANGES @@ -2,7 +2,9 @@ jsoup changelog *** Release 1.14.2 [PENDING] * Bugfix: corrected a potential case of the parser input stream not being closed immediately on a read exception. - + + * Bugfix: when making a HTTP POST, if the request write fails, make sure the connection is immediately cleaned up. + * Bugfix [Fuzz]: fixed a slow parse when a tag has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 16d3fd7954..6721d9d905 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -857,8 +857,10 @@ else if (methodHasBody) Response res = null; try { conn.connect(); - if (conn.getDoOutput()) - writePost(req, conn.getOutputStream(), mimeBoundary); + if (conn.getDoOutput()) { + try { writePost(req, conn.getOutputStream(), mimeBoundary); } + catch (IOException e) { conn.disconnect(); throw e; } + } int status = conn.getResponseCode(); res = new Response(conn, req, previousResponse); From 607995d903570968ca0c41a82d7a37b89b979a4b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 16:22:20 +1000 Subject: [PATCH 554/774] Make sure the output stream is closed too --- src/main/java/org/jsoup/helper/HttpConnection.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 6721d9d905..3ba82b4c2b 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -858,8 +858,10 @@ else if (methodHasBody) try { conn.connect(); if (conn.getDoOutput()) { - try { writePost(req, conn.getOutputStream(), mimeBoundary); } + OutputStream out = conn.getOutputStream(); + try { writePost(req, out, mimeBoundary); } catch (IOException e) { conn.disconnect(); throw e; } + finally { out.close(); } } int status = conn.getResponseCode(); From 2dc914f4e40b5a6d6018572231d26c30c40b0d4f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 16:26:16 +1000 Subject: [PATCH 555/774] Make sure Entity reader is closed immediately Doesn't really mean much as this is just a string - but maybe a useful GC hint. --- src/main/java/org/jsoup/nodes/Entities.java | 55 +++++++++++---------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index dee8d9f141..caa78e2932 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -310,37 +310,40 @@ private static void load(EscapeMode e, String pointsData, int size) { int i = 0; CharacterReader reader = new CharacterReader(pointsData); + try { + while (!reader.isEmpty()) { + // NotNestedLessLess=10913,824;1887& - while (!reader.isEmpty()) { - // NotNestedLessLess=10913,824;1887& - - final String name = reader.consumeTo('='); - reader.advance(); - final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); - final char codeDelim = reader.current(); - reader.advance(); - final int cp2; - if (codeDelim == ',') { - cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); + final String name = reader.consumeTo('='); + reader.advance(); + final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); + final char codeDelim = reader.current(); + reader.advance(); + final int cp2; + if (codeDelim == ',') { + cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); + reader.advance(); + } else { + cp2 = empty; + } + final String indexS = reader.consumeTo('&'); + final int index = Integer.parseInt(indexS, codepointRadix); reader.advance(); - } else { - cp2 = empty; - } - final String indexS = reader.consumeTo('&'); - final int index = Integer.parseInt(indexS, codepointRadix); - reader.advance(); - e.nameKeys[i] = name; - e.codeVals[i] = cp1; - e.codeKeys[index] = cp1; - e.nameVals[index] = name; + e.nameKeys[i] = name; + e.codeVals[i] = cp1; + e.codeKeys[index] = cp1; + e.nameVals[index] = name; - if (cp2 != empty) { - multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); + if (cp2 != empty) { + multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); + } + i++; } - i++; - } - Validate.isTrue(i == size, "Unexpected count of entities loaded"); + Validate.isTrue(i == size, "Unexpected count of entities loaded"); + } finally { + reader.close(); + } } } From 049204c2d47eb26315b519de65c89b692f92230c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 16:29:59 +1000 Subject: [PATCH 556/774] Safety validations --- src/main/java/org/jsoup/select/NodeTraversor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index 74b66b4e26..ae2b458002 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -18,6 +18,9 @@ public class NodeTraversor { * @param root the root node point to traverse. */ public static void traverse(NodeVisitor visitor, Node root) { + Validate.notNull(visitor); + Validate.notNull(root); + Node node = root; Node parent; // remember parent to find nodes that get replaced in .head int depth = 0; From 2676f413ede8139d58bf6ea43aae8c82d0b301ff Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 11 Jul 2021 17:00:05 +1000 Subject: [PATCH 557/774] Updated resetInsertionMode Fixes #1491 Need to add support for template tags --- CHANGES | 3 +++ .../org/jsoup/parser/HtmlTreeBuilder.java | 24 +++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 02bd724a30..411e6b8299 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,9 @@ jsoup changelog * Bugfix: when making a HTTP POST, if the request write fails, make sure the connection is immediately cleaned up. + * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements + <https://github.com/jhy/jsoup/issues/1491> + * Bugfix [Fuzz]: fixed a slow parse when a tag has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 835fa323f0..ed533b8d92 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -438,17 +438,20 @@ private void replaceInQueue(ArrayList<Element> queue, Element out, Element in) { } void resetInsertionMode() { + // https://html.spec.whatwg.org/multipage/parsing.html#the-insertion-mode boolean last = false; for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); if (pos == 0) { last = true; - node = contextElement; + if (fragmentParsing) + node = contextElement; } String name = node != null ? node.normalName() : ""; if ("select".equals(name)) { transition(HtmlTreeBuilderState.InSelect); - break; // frag + // todo - should loop up (with some limit) and check for table or template hits + break; } else if (("td".equals(name) || "th".equals(name) && !last)) { transition(HtmlTreeBuilderState.InCell); break; @@ -463,25 +466,26 @@ void resetInsertionMode() { break; } else if ("colgroup".equals(name)) { transition(HtmlTreeBuilderState.InColumnGroup); - break; // frag + break; } else if ("table".equals(name)) { transition(HtmlTreeBuilderState.InTable); break; - } else if ("head".equals(name)) { - transition(HtmlTreeBuilderState.InBody); - break; // frag + // todo - template + } else if ("head".equals(name) && !last) { + transition(HtmlTreeBuilderState.InHead); + break; } else if ("body".equals(name)) { transition(HtmlTreeBuilderState.InBody); break; } else if ("frameset".equals(name)) { transition(HtmlTreeBuilderState.InFrameset); - break; // frag + break; } else if ("html".equals(name)) { - transition(HtmlTreeBuilderState.BeforeHead); - break; // frag + transition(headElement == null ? HtmlTreeBuilderState.BeforeHead : HtmlTreeBuilderState.AfterHead); + break; } else if (last) { transition(HtmlTreeBuilderState.InBody); - break; // frag + break; } } } From 32d9878413a4cf5d392e57d8325153b3a6aa8c3c Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 11 Jul 2021 21:27:02 +1000 Subject: [PATCH 558/774] Testcase for #1557 --- src/test/java/org/jsoup/parser/ParserTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/jsoup/parser/ParserTest.java b/src/test/java/org/jsoup/parser/ParserTest.java index f77b230616..7a97f2c680 100644 --- a/src/test/java/org/jsoup/parser/ParserTest.java +++ b/src/test/java/org/jsoup/parser/ParserTest.java @@ -1,7 +1,12 @@ package org.jsoup.parser; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; public class ParserTest { @@ -22,4 +27,12 @@ public void unescapeEntitiesHandlesLargeInput() { String body = longBody.toString(); assertEquals(body, Parser.unescapeEntities(body, false)); } + + @Test + public void testUtf8() throws IOException { + // testcase for https://github.com/jhy/jsoup/issues/1557. no repro. + Document parsed = Jsoup.parse(new ByteArrayInputStream("<p>H\u00E9llo, w\u00F6rld!".getBytes("UTF-8")), null, ""); + String text = parsed.selectFirst("p").wholeText(); + assertEquals(text, "H\u00E9llo, w\u00F6rld!"); + } } From 6b04287f66d7677e9b2f275123f2dff4b934c5f2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 12 Jul 2021 14:05:50 +1000 Subject: [PATCH 559/774] Fix a potential stack overflow in InTable Fixes #1577 When in InTable state, make sure resetInsertionMode actually broke out of InTable before trying to reprocess the token. Otherwise, insert directly --- CHANGES | 3 ++ pom.xml | 4 +++ .../jsoup/parser/HtmlTreeBuilderState.java | 32 +++++++++++-------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 411e6b8299..d66f69e620 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ jsoup changelog * Bugfiz [Fuzz]: malformed HTML could result in null elements on stack <https://github.com/jhy/jsoup/issues/1579> + * Bugfix [Fuzz]: malformed deeply nested table elements could create a stack overflow. + <https://github.com/jhy/jsoup/issues/1577> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/pom.xml b/pom.xml index a06f296b15..ab146c3ca3 100644 --- a/pom.xml +++ b/pom.xml @@ -166,6 +166,10 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M3</version> + <configuration> + <!-- smaller stack to find stack overflows --> + <argLine>-Xss256k</argLine> + </configuration> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 3d5e1d45e8..8a0f77efd5 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -903,7 +903,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { }, InTable { boolean process(Token t, HtmlTreeBuilder tb) { - if (t.isCharacter()) { + if (t.isCharacter() && inSorted(tb.currentElement().normalName(), InTableFoster)) { tb.newPendingTableCharacters(); tb.markInsertionMode(); tb.transition(InTableText); @@ -927,6 +927,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.insert(startTag); tb.transition(InColumnGroup); } else if (name.equals("col")) { + tb.clearStackToTableContext(); tb.processStartTag("colgroup"); return tb.process(t); } else if (inSorted(name, InTableToBody)) { @@ -934,13 +935,23 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.insert(startTag); tb.transition(InTableBody); } else if (inSorted(name, InTableAddBody)) { + tb.clearStackToTableContext(); tb.processStartTag("tbody"); return tb.process(t); } else if (name.equals("table")) { tb.error(this); - boolean processed = tb.processEndTag("table"); - if (processed) // only ignored if in fragment + if (!tb.inTableScope(name)) { // ignore it + return false; + } else { + tb.popStackToClose(name); + tb.resetInsertionMode(); + if (tb.state() == InTable) { + // not per spec - but haven't transitioned out of table. so try something else + tb.insert(startTag); + return true; + } return tb.process(t); + } } else if (inSorted(name, InTableToHead)) { return tb.process(t, InHead); } else if (name.equals("input")) { @@ -970,8 +981,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else { tb.popStackToClose("table"); + tb.resetInsertionMode(); } - tb.resetInsertionMode(); } else if (inSorted(name, InTableEndErr)) { tb.error(this); return false; @@ -989,15 +1000,10 @@ boolean process(Token t, HtmlTreeBuilder tb) { boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.error(this); - boolean processed; - if (inSorted(tb.currentElement().normalName(), InTableFoster)) { - tb.setFosterInserts(true); - processed = tb.process(t, InBody); - tb.setFosterInserts(false); - } else { - processed = tb.process(t, InBody); - } - return processed; + tb.setFosterInserts(true); + tb.process(t, InBody); + tb.setFosterInserts(false); + return true; } }, InTableText { From 426e35ec1cd48cd2802e031fc8b4ef4a42de0482 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 12 Jul 2021 20:55:59 +1000 Subject: [PATCH 560/774] Fix the Evaluator for wildcard namespace queries The *|el wildcard namespace selector now also matches elements with no namespace. Fixes #1565 --- CHANGES | 3 +++ src/main/java/org/jsoup/select/QueryParser.java | 6 +++++- src/main/java/org/jsoup/select/Selector.java | 2 +- src/test/java/org/jsoup/select/SelectorTest.java | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index d66f69e620..b86ecbf79e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.14.2 [PENDING] + * Bugfix: the *|el wildcard namespace selector now also matches elements with no namespace. + <https://github.com/jhy/jsoup/issues/1565> + * Bugfix: corrected a potential case of the parser input stream not being closed immediately on a read exception. * Bugfix: when making a HTTP POST, if the request write fails, make sure the connection is immediately cleaned up. diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index f06d10d3fa..3e3b4105d2 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -237,7 +237,11 @@ private void byTag() { // namespaces: wildcard match equals(tagName) or ending in ":"+tagName if (tagName.startsWith("*|")) { - evals.add(new CombiningEvaluator.Or(new Evaluator.Tag(tagName), new Evaluator.TagEndsWith(tagName.replace("*|", ":")))); + String plainTag = tagName.substring(2); // strip *| + evals.add(new CombiningEvaluator.Or( + new Evaluator.Tag(plainTag), + new Evaluator.TagEndsWith(tagName.replace("*|", ":"))) + ); } else { // namespaces: if element name is "abc:def", selector must be "abc|def", so flip: if (tagName.contains("|")) diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 1ac4f1d881..1fafbdb39f 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -25,7 +25,7 @@ * <tr><th align="left">Pattern</th><th align="left">Matches</th><th align="left">Example</th></tr> * <tr><td><code>*</code></td><td>any element</td><td><code>*</code></td></tr> * <tr><td><code>tag</code></td><td>elements with the given tag name</td><td><code>div</code></td></tr> - * <tr><td><code>*|E</code></td><td>elements of type E in any namespace <i>ns</i></td><td><code>*|name</code> finds <code>&lt;fb:name&gt;</code> elements</td></tr> + * <tr><td><code>*|E</code></td><td>elements of type E in any namespace (including non-namespaced)</td><td><code>*|name</code> finds <code>&lt;fb:name&gt;</code> and <code>&lt;name&gt;</code> elements</td></tr> * <tr><td><code>ns|E</code></td><td>elements of type E in the namespace <i>ns</i></td><td><code>fb|name</code> finds <code>&lt;fb:name&gt;</code> elements</td></tr> * <tr><td><code>#id</code></td><td>elements with attribute ID of "id"</td><td><code>div#wrap</code>, <code>#logo</code></td></tr> * <tr><td><code>.class</code></td><td>elements with a class name of "class"</td><td><code>div.left</code>, <code>.result</code></td></tr> diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index ac46ee7fef..def16ed234 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -996,4 +996,20 @@ public void selectFirstLevelChildrenOnly() { assertEquals("One Two", spans.get(0).text()); assertEquals("Three Four", spans.get(1).text()); } + + @Test + public void wildcardNamespaceMatchesNoNamespace() { + // https://github.com/jhy/jsoup/issues/1565 + String xml = "<package><meta>One</meta><opf:meta>Two</opf:meta></package>"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + + Elements metaEls = doc.select("meta"); + assertEquals(1, metaEls.size()); + assertEquals("One", metaEls.get(0).text()); + + Elements nsEls = doc.select("*|meta"); + assertEquals(2, nsEls.size()); + assertEquals("One", nsEls.get(0).text()); + assertEquals("Two", nsEls.get(1).text()); + } } From ab34da41de20717b3e556d5562143c918cc02fbc Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 13 Jul 2021 13:42:31 +1000 Subject: [PATCH 561/774] Update Attributes.size() to be O(1) vs O(n) We were excluding internalKeys from the size, but that meant scanning through the keys array on every invocation. Updated to reflect the total size instead - can use asList.size() for the non-internal count if ever actually required. Internal keys are seldom used (base URL on document is only current use) so low impact change. Preferred this than adding another int to track and other methods - keeps object size to 24 bytes. --- src/main/java/org/jsoup/nodes/Attributes.java | 11 ++++------- src/test/java/org/jsoup/nodes/AttributesTest.java | 6 +++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index b2dc18efb9..6e0d789887 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -46,6 +46,7 @@ public class Attributes implements Iterable<Attribute>, Cloneable { static final int NotFound = -1; private static final String EmptyString = ""; + // the number of instance fields is kept as low as possible giving an object size of 24 bytes private int size = 0; // number of slots used (not total capacity, which is keys.length) String[] keys = new String[InitialCapacity]; String[] vals = new String[InitialCapacity]; @@ -246,16 +247,12 @@ public boolean hasDeclaredValueForKeyIgnoreCase(String key) { } /** - Get the number of attributes in this set. + Get the number of attributes in this set, including any jsoup internal-only attributes. Internal attributes are + excluded from the {@link #html()}, {@link #asList()}, and {@link #iterator()} methods. @return size */ public int size() { - int s = 0; - for (int i = 0; i < size; i++) { - if (!isInternalKey(keys[i])) - s++; - } - return s; + return size; } /** diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 84d9ed78c0..d2f986b096 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -225,6 +225,10 @@ public void testBoolean() { a.put(Attributes.internalKey("baseUri"), "example.com"); a.put(Attributes.internalKey("another"), "example.com"); - assertEquals(2, a.size()); + a.put(Attributes.internalKey("last"), "example.com"); + a.remove(Attributes.internalKey("last")); + + assertEquals(4, a.size()); + assertEquals(2, a.asList().size()); // excluded from lists } } From 1f0b68f5b620928d8f4bfd8658aa7628ee49d1de Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 14 Jul 2021 10:04:31 +1000 Subject: [PATCH 562/774] Perf: limit attribute count per element to 512 Prevents runaway situations Should improve #1578 --- CHANGES | 4 ++++ src/main/java/org/jsoup/parser/Token.java | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b86ecbf79e..d60b728bb6 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,10 @@ jsoup changelog * Bugfix [Fuzz]: malformed deeply nested table elements could create a stack overflow. <https://github.com/jhy/jsoup/issues/1577> + * Bugfix [Fuzz]: Speed optimized malformed HTML creating elements with thousands of elements - limit the attribute + count per element when parsing to 512 (in real-world HTML, P99 is ~ 8). + <https://github.com/jhy/jsoup/issues/1578> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 9ade8e0d0e..0b3cb6c5f7 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -97,11 +97,16 @@ Tag reset() { return this; } + /* Limits runaway crafted HTML from spewing attributes and getting a little sluggish in ensureCapacity. + Real-world HTML will P99 around 8 attributes, so plenty of headroom. Implemented here and not in the Attributes + object so that API users can add more if ever required. */ + private static final int MaxAttributes = 512; + final void newAttribute() { if (attributes == null) attributes = new Attributes(); - if (pendingAttributeName != null) { + if (pendingAttributeName != null && attributes.size() < MaxAttributes) { // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here pendingAttributeName = pendingAttributeName.trim(); if (pendingAttributeName.length() > 0) { From bf0e74d7cc5ff794efba7860c95b1893ade9efd8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 14 Jul 2021 10:54:24 +1000 Subject: [PATCH 563/774] Minor code cleanup in flyweight --- .../org/jsoup/parser/CharacterReader.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 35f52fa73e..e229749460 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -555,7 +555,7 @@ public String toString() { } /** - * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks. + * Caches short strings, as a flyweight pattern, to reduce GC load. Just for this doc, to prevent leaks. * <p /> * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list. * That saves both having to create objects as hash keys, and running through the entry list, at the expense of @@ -569,27 +569,22 @@ private static String cacheString(final char[] charBuf, final String[] stringCac return ""; // calculate hash: - int hash = 31 * count; - int offset = start; + int hash = 0; for (int i = 0; i < count; i++) { - hash = 31 * hash + charBuf[offset++]; + hash = 31 * hash + charBuf[start + i]; } // get from cache final int index = hash & stringCacheSize - 1; String cached = stringCache[index]; - if (cached == null) { // miss, add + if (cached != null && rangeEquals(charBuf, start, count, cached)) // positive hit + return cached; + else { cached = new String(charBuf, start, count); - stringCache[index] = cached; - } else { // hashcode hit, check equality - if (rangeEquals(charBuf, start, count, cached)) { // hit - return cached; - } else { // hashcode conflict - cached = new String(charBuf, start, count); - stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again - } + stringCache[index] = cached; // add or replace, assuming most recently used are most likely to recur next } + return cached; } From 0d803c9556a5e81792fe3b54c452d38d44089c28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:56:12 +1000 Subject: [PATCH 564/774] Bump maven-resources-plugin from 3.0.1 to 3.2.0 (#1582) Bumps [maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.0.1 to 3.2.0. - [Release notes](https://github.com/apache/maven-resources-plugin/releases) - [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.0.1...maven-resources-plugin-3.2.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-resources-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ab146c3ca3..4005bdcb47 100644 --- a/pom.xml +++ b/pom.xml @@ -156,7 +156,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> - <version>3.0.1</version> + <version>3.2.0</version> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> From 21389ed8f6ffd9a1cb5e3ca3d1e15e34f83d01b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:57:04 +1000 Subject: [PATCH 565/774] Bump maven-javadoc-plugin from 3.1.0 to 3.3.0 (#1583) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.1.0 to 3.3.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.1.0...maven-javadoc-plugin-3.3.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4005bdcb47..d90abeb6fe 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.1.0</version> + <version>3.3.0</version> <configuration> <doclint>none</doclint> <source>7</source> From 300c521fcc3fce49a199a59f672a62111ee27171 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:57:29 +1000 Subject: [PATCH 566/774] Bump junit-jupiter from 5.6.0 to 5.7.2 (#1584) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.6.0 to 5.7.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.6.0...r5.7.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d90abeb6fe..9fefc2e578 100644 --- a/pom.xml +++ b/pom.xml @@ -308,7 +308,7 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> - <version>5.6.0</version> + <version>5.7.2</version> <scope>test</scope> </dependency> From e67e5fec16200856d437c98506f19b832c8f6a70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:58:19 +1000 Subject: [PATCH 567/774] Bump maven-surefire-plugin from 3.0.0-M3 to 3.0.0-M5 (#1585) Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M3 to 3.0.0-M5. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M3...surefire-3.0.0-M5) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fefc2e578..733a6cc703 100644 --- a/pom.xml +++ b/pom.xml @@ -165,7 +165,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>3.0.0-M3</version> + <version>3.0.0-M5</version> <configuration> <!-- smaller stack to find stack overflows --> <argLine>-Xss256k</argLine> From d3922f5ad4ac03226f5ad3f95a433588ddc73c05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 10:58:43 +1000 Subject: [PATCH 568/774] Bump maven-jar-plugin from 3.0.2 to 3.2.0 (#1586) Bumps [maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.0.2 to 3.2.0. - [Release notes](https://github.com/apache/maven-jar-plugin/releases) - [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.0.2...maven-jar-plugin-3.2.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-jar-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 733a6cc703..a3f9fd29b6 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> <configuration> <archive> <manifestEntries> From fce241b57f1e0292b143a11d31b8abf9ad949398 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 14 Jul 2021 11:35:39 +1000 Subject: [PATCH 569/774] Skip empty attribute values when serializing XML processing instructions Fixes #770 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/XmlDeclaration.java | 12 ++++++++++-- .../java/org/jsoup/parser/XmlTreeBuilderTest.java | 10 ++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index d60b728bb6..c07a143b4e 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ jsoup changelog * Bugfix: when making a HTTP POST, if the request write fails, make sure the connection is immediately cleaned up. + * Bugfix: in the XML parser, XML processing instructions without attributes would be serialized as if they did. + <https://github.com/jhy/jsoup/issues/770> + * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements <https://github.com/jhy/jsoup/issues/1491> diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index d7906eb6a6..6ab9b16241 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -52,9 +52,17 @@ public String getWholeDeclaration() { private void getWholeDeclaration(Appendable accum, Document.OutputSettings out) throws IOException { for (Attribute attribute : attributes()) { - if (!attribute.getKey().equals(nodeName())) { // skips coreValue (name) + String key = attribute.getKey(); + String val = attribute.getValue(); + if (!key.equals(nodeName())) { // skips coreValue (name) accum.append(' '); - attribute.html(accum, out); + // basically like Attribute, but skip empty vals in XML + accum.append(key); + if (!val.isEmpty()) { + accum.append("=\""); + Entities.escape(accum, val, out, true, false, false); + accum.append('"'); + } } } } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index d2527a9083..8fb0fbd8a8 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -152,6 +152,16 @@ public void testParseDeclarationAttributes() { assertEquals("<?xml version=\"1\" encoding=\"UTF-8\" something=\"else\"?>", decl.outerHtml()); } + @Test + public void testParseDeclarationWithoutAttributes() { + String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?myProcessingInstruction My Processing instruction.?>"; + Document doc = Jsoup.parse(xml, "", Parser.xmlParser()); + XmlDeclaration decl = (XmlDeclaration) doc.childNode(2); + assertEquals("myProcessingInstruction", decl.name()); + assertTrue(decl.hasAttr("My")); + assertEquals("<?myProcessingInstruction My Processing instruction.?>", decl.outerHtml()); + } + @Test public void caseSensitiveDeclaration() { String xml = "<?XML version='1' encoding='UTF-8' something='else'?>"; From 009dbb16861978db1f25272f12f61343cc11055e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 15 Jul 2021 16:01:10 +1000 Subject: [PATCH 570/774] Perf increase when attribute name is packed with thousands of nullchars Fixes #1580 for attribute names --- CHANGES | 2 +- src/main/java/org/jsoup/parser/Token.java | 2 ++ .../java/org/jsoup/parser/TokeniserState.java | 9 ++------- .../org/jsoup/integration/FuzzFixesTest.java | 12 +++++++++++- .../resources/fuzztests/1580-attrname.html.gz | Bin 0 -> 1184 bytes 5 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/fuzztests/1580-attrname.html.gz diff --git a/CHANGES b/CHANGES index c07a143b4e..2d23a6870a 100644 --- a/CHANGES +++ b/CHANGES @@ -14,7 +14,7 @@ jsoup changelog * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements <https://github.com/jhy/jsoup/issues/1491> - * Bugfix [Fuzz]: fixed a slow parse when a tag has thousands of null characters in it. + * Bugfix [Fuzz]: fixed a slow parse when a tag or an attribute name has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> * Bugfix [Fuzz]: the adoption agency algorithm can have an incorrect bookmark position diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 0b3cb6c5f7..d1b38b91e1 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -181,6 +181,8 @@ final void appendTagName(char append) { } final void appendAttributeName(String append) { + // might have null chars because we eat in one pass - need to replace with null replacement character + append = append.replace(TokeniserState.nullChar, Tokeniser.replacementChar); pendingAttributeName = pendingAttributeName == null ? append : pendingAttributeName.concat(append); } diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index e405c1b7f0..845201fb1f 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -144,7 +144,6 @@ void read(Tokeniser t, CharacterReader r) { // from < or </ in data, will have start or end tag pending void read(Tokeniser t, CharacterReader r) { // previous TagOpen state did NOT consume, will have a letter char in current - //String tagName = r.consumeToAnySorted(tagCharsSorted).toLowerCase(); String tagName = r.consumeTagName(); t.tagPending.appendTagName(tagName); @@ -608,7 +607,7 @@ void read(Tokeniser t, CharacterReader r) { AttributeName { // from before attribute name void read(Tokeniser t, CharacterReader r) { - String name = r.consumeToAnySorted(attributeNameCharsSorted); + String name = r.consumeToAnySorted(attributeNameCharsSorted); // spec deviate - consume and emit nulls in one hit vs stepping t.tagPending.appendAttributeName(name); char c = r.consume(); @@ -630,10 +629,6 @@ void read(Tokeniser t, CharacterReader r) { t.emitTagPending(); t.transition(Data); break; - case nullChar: - t.error(this); - t.tagPending.appendAttributeName(replacementChar); - break; case eof: t.eofError(this); t.transition(Data); @@ -1631,7 +1626,7 @@ void read(Tokeniser t, CharacterReader r) { static final char nullChar = '\u0000'; // char searches. must be sorted, used in inSorted. MUST update TokenisetStateTest if more arrays are added. - static final char[] attributeNameCharsSorted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '\'', '/', '<', '=', '>'}; + static final char[] attributeNameCharsSorted = new char[]{'\t', '\n', '\f', '\r', ' ', '"', '\'', '/', '<', '=', '>'}; static final char[] attributeValueUnquoted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '&', '\'', '<', '=', '>', '`'}; private static final char replacementChar = Tokeniser.replacementChar; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index db45b41965..122cb59d19 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -104,7 +104,6 @@ public void scope1579() { @Test public void overflow1577() throws IOException { // https://github.com/jhy/jsoup/issues/1577 - // no repro - fixed elsewhere? File in = ParseTest.getFile("/fuzztests/1577.html.gz"); Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); @@ -112,4 +111,15 @@ public void overflow1577() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void parseTimeout36150() throws IOException { + File in = ParseTest.getFile("/fuzztests/1580-attrname.html.gz"); + // pretty much 1MB of null chars in text head + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1580-attrname.html.gz b/src/test/resources/fuzztests/1580-attrname.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..952d86bd91e9ddd8ab910a580aec0f037abd3eff GIT binary patch literal 1184 zcmb2|=HR$-;(Y=Gx1p(pfo@_+Nl{*6ZmM2JNp23q+w+E8ha3bP0&~TS+GP|YB05(j zwkCKSShDf@p<~C6ovsMx&ed_rVTqjjy(>{{+mGNDy=k>q%@ReDE{E5O?q0+3xwLY2 z>D41SGGen`?@n7C&~ihMz5e|iD+UI;q!~aG2KHUlDTT2|94%kyDLaBZeL#Ei-@^JE w@BA+Rp7j1+to(fc{hzKZXBt5vFl@d0LA`AyGv&T}usw!RCFHvo1A_nq0F;4Dh5!Hn literal 0 HcmV?d00001 From d4f91c5eba9be87cd7d6ca90275e2200a4a6ff65 Mon Sep 17 00:00:00 2001 From: Hulmes <60814832+suarez12138@users.noreply.github.com> Date: Thu, 15 Jul 2021 14:51:42 +0800 Subject: [PATCH 571/774] Support for match with quoted pattern (#1536) * Fix #986 * Doc and variable name tweak For #1536 Co-authored-by: Jonathan Hedley <jonathan@hedley.net> --- src/main/java/org/jsoup/parser/TokenQueue.java | 9 ++++++++- src/main/java/org/jsoup/select/Selector.java | 2 ++ src/test/java/org/jsoup/parser/TokenQueueTest.java | 11 +++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index f269051708..42c30668fa 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -264,6 +264,7 @@ public String chompBalanced(char open, char close) { char last = 0; boolean inSingleQuote = false; boolean inDoubleQuote = false; + boolean inRegexQE = false; // regex \Q .. \E escapes from Pattern.quote() do { if (isEmpty()) break; @@ -273,8 +274,10 @@ public String chompBalanced(char open, char close) { inSingleQuote = !inSingleQuote; else if (c == '"' && c != open && !inSingleQuote) inDoubleQuote = !inDoubleQuote; - if (inSingleQuote || inDoubleQuote) + if (inSingleQuote || inDoubleQuote || inRegexQE){ + last = c; continue; + } if (c == open) { depth++; @@ -283,6 +286,10 @@ else if (c == '"' && c != open && !inSingleQuote) } else if (c == close) depth--; + } else if (c == 'Q') { + inRegexQE = true; + } else if (c == 'E') { + inRegexQE = false; } if (depth > 0 && last != 0) diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 1fafbdb39f..0afd8c77df 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -73,6 +73,8 @@ * <tr><td><code>:empty</code></td><td>elements that have no children at all</td><td></td></tr> * </table> * + * <p>A word on using regular expressions in these selectors: depending on the content of the regex, you will need to quote the pattern using <b><code>Pattern.quote("regex")</code></b> for it to parse correclty through both the selector parser and the regex parser. E.g. <code>String query = "div:matches(" + Pattern.quote(regex) + ");"</code>.</p> + * * @author Jonathan Hedley, jonathan@hedley.net * @see Element#select(String) */ diff --git a/src/test/java/org/jsoup/parser/TokenQueueTest.java b/src/test/java/org/jsoup/parser/TokenQueueTest.java index 2690c4bf97..463a19e4f4 100644 --- a/src/test/java/org/jsoup/parser/TokenQueueTest.java +++ b/src/test/java/org/jsoup/parser/TokenQueueTest.java @@ -1,8 +1,11 @@ package org.jsoup.parser; import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.junit.jupiter.api.Test; +import java.util.regex.Pattern; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -95,4 +98,12 @@ public void chompBalancedThrowIllegalArgumentException() { assertEquals("Did not find balanced marker at 'something(or another)) else'", expected.getMessage()); } } + + @Test + public void testQuotedPattern() { + final Document doc = Jsoup.parse("<div>\\) foo1</div><div>( foo2</div><div>1) foo3</div>"); + assertEquals("\n\\) foo1",doc.select("div:matches(" + Pattern.quote("\\)") + ")").get(0).childNode(0).toString()); + assertEquals("\n( foo2",doc.select("div:matches(" + Pattern.quote("(") + ")").get(0).childNode(0).toString()); + assertEquals("\n1) foo3",doc.select("div:matches(" + Pattern.quote("1)") + ")").get(0).childNode(0).toString()); + } } From b80008f55f71c913f32f74aebf1e85e7bd6baebd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 15 Jul 2021 16:53:11 +1000 Subject: [PATCH 572/774] Changelog for Pattern.quote #1536 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 2d23a6870a..2454f52341 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.14.2 [PENDING] + * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. + <https://github.com/jhy/jsoup/pull/1536> + * Bugfix: the *|el wildcard namespace selector now also matches elements with no namespace. <https://github.com/jhy/jsoup/issues/1565> From 5fc8403e5019db62f50057366a96174ef49a1761 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 15 Jul 2021 16:58:23 +1000 Subject: [PATCH 573/774] Deprecated some old unused methods in TokenQueue --- CHANGES | 2 +- src/main/java/org/jsoup/parser/TokenQueue.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 2454f52341..262503eab4 100644 --- a/CHANGES +++ b/CHANGES @@ -3,7 +3,7 @@ jsoup changelog *** Release 1.14.2 [PENDING] * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. <https://github.com/jhy/jsoup/pull/1536> - + * Bugfix: the *|el wildcard namespace selector now also matches elements with no namespace. <https://github.com/jhy/jsoup/issues/1565> diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 42c30668fa..614ade1914 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -38,7 +38,9 @@ private int remainingLength() { /** * Retrieves but does not remove the first character from the queue. * @return First character, or 0 if empty. + *@deprecated unused and will be removed in 1.15.x */ + @Deprecated public char peek() { return isEmpty() ? 0 : queue.charAt(pos); } @@ -46,7 +48,9 @@ public char peek() { /** Add a character to the start of the queue (will be the next character retrieved). @param c character to add + @deprecated unused and will be removed in 1.15.x */ + @Deprecated public void addFirst(Character c) { addFirst(c.toString()); } @@ -74,7 +78,9 @@ public boolean matches(String seq) { * Case sensitive match test. * @param seq string to case sensitively check for * @return true if matched, false if not + * @deprecated unused and will be removed in 1.15.x */ + @Deprecated public boolean matchesCS(String seq) { return queue.startsWith(seq, pos); } @@ -104,6 +110,10 @@ public boolean matchesAny(char... seq) { return false; } + /** + @deprecated unused and will be removed in 1.15.x + */ + @Deprecated public boolean matchesStartTag() { // micro opt for matching "<x" return (remainingLength() >= 2 && queue.charAt(pos) == '<' && Character.isLetter(queue.charAt(pos+1))); @@ -351,7 +361,9 @@ public String consumeWord() { * Consume an tag name off the queue (word or :, _, -) * * @return tag name + * @deprecated unused and will be removed in 1.15.x */ + @Deprecated public String consumeTagName() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny(':', '_', '-'))) @@ -389,7 +401,9 @@ public String consumeCssIdentifier() { /** Consume an attribute key off the queue (letter, digit, -, _, :") @return attribute key + @deprecated unused and will be removed in 1.15.x */ + @Deprecated public String consumeAttributeKey() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny('-', '_', ':'))) From 4ac8a93b0a85f666941ef0198556d1e2395991eb Mon Sep 17 00:00:00 2001 From: Ryderxxx <39330401+Ryderxxx@users.noreply.github.com> Date: Thu, 15 Jul 2021 15:29:24 +0800 Subject: [PATCH 574/774] Consume empty sub queries Fix #issue1425 --- src/main/java/org/jsoup/select/QueryParser.java | 5 ++++- .../java/org/jsoup/select/QueryParserTest.java | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 3e3b4105d2..fb2c0797dd 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -147,7 +147,10 @@ private String consumeSubQuery() { else if (tq.matches("[")) sq.append("[").append(tq.chompBalanced('[', ']')).append("]"); else if (tq.matchesAny(combinators)) - break; + if (sq.length() > 0) + break; + else + tq.consume(); else sq.append(tq.consume()); } diff --git a/src/test/java/org/jsoup/select/QueryParserTest.java b/src/test/java/org/jsoup/select/QueryParserTest.java index 34ee8a9fcc..8c4db2348b 100644 --- a/src/test/java/org/jsoup/select/QueryParserTest.java +++ b/src/test/java/org/jsoup/select/QueryParserTest.java @@ -1,5 +1,7 @@ package org.jsoup.select; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -10,6 +12,19 @@ * @author Jonathan Hedley */ public class QueryParserTest { + + @Test public void testConsumeSubQuery() { + Document doc = Jsoup.parse("<html><head>h <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>" + + "<li><strong>l1</strong></li>" + + "<a><li><strong>l2</strong></li></a>" + + "<p><strong>yes</strong></p>" + + "</body></html>"); + assertEquals("l1 l2 yes", doc.body().select(">p>strong,>*>li>strong").text()); + assertEquals("l2 yes", doc.select("body>p>strong,body>*>li>strong").text()); + assertEquals("yes", doc.select(">body>*>li>strong,>body>p>strong").text()); + assertEquals("l2", doc.select(">body>p>strong,>body>*>li>strong").text()); + } + @Test public void testOrGetsCorrectPrecedence() { // tests that a selector "a b, c d, e f" evals to (a AND b) OR (c AND d) OR (e AND f)" // top level or, three child ands From ee8cfdafaa85d75e3593f2ba101663909117c62a Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Thu, 15 Jul 2021 14:28:02 +0200 Subject: [PATCH 575/774] Ignore major Jetty updates on dependabot. (#1581) --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e365683549..7761dccad6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,9 @@ updates: directory: / schedule: interval: weekly + ignore: + # Jetty 9.x needed for JDK8 compatibility; it still receives security updates + - dependency-name: "jetty-server" + update-types: ["version-update:semver-major"] + - dependency-name: "jetty-servlet" + update-types: ["version-update:semver-major"] From 034f1f75466ca5cd3700cd62024d7d6b31526498 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jul 2021 22:40:29 +1000 Subject: [PATCH 576/774] Bump animal-sniffer-maven-plugin from 1.16 to 1.20 (#1590) Bumps [animal-sniffer-maven-plugin](https://github.com/mojohaus/animal-sniffer) from 1.16 to 1.20. - [Release notes](https://github.com/mojohaus/animal-sniffer/releases) - [Commits](https://github.com/mojohaus/animal-sniffer/compare/animal-sniffer-parent-1.16...animal-sniffer-parent-1.20) --- updated-dependencies: - dependency-name: org.codehaus.mojo:animal-sniffer-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a3f9fd29b6..9fee61ea44 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ <!-- Ensure Java 8 and Android 10 API compatibility --> <groupId>org.codehaus.mojo</groupId> <artifactId>animal-sniffer-maven-plugin</artifactId> - <version>1.16</version> + <version>1.20</version> <executions> <execution> <id>animal-sniffer</id> From bc0ee7d94c386e04fc06657aa05ac256a1d17f0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jul 2021 22:44:19 +1000 Subject: [PATCH 577/774] Bump japicmp-maven-plugin from 0.15.2 to 0.15.3 (#1588) Bumps [japicmp-maven-plugin](https://github.com/siom79/japicmp) from 0.15.2 to 0.15.3. - [Release notes](https://github.com/siom79/japicmp/releases) - [Changelog](https://github.com/siom79/japicmp/blob/master/release.py) - [Commits](https://github.com/siom79/japicmp/compare/japicmp-base-0.15.2...japicmp-base-0.15.3) --- updated-dependencies: - dependency-name: com.github.siom79.japicmp:japicmp-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fee61ea44..2730ee2df4 100644 --- a/pom.xml +++ b/pom.xml @@ -191,7 +191,7 @@ <!-- API version compat check - https://siom79.github.io/japicmp/ --> <groupId>com.github.siom79.japicmp</groupId> <artifactId>japicmp-maven-plugin</artifactId> - <version>0.15.2</version> + <version>0.15.3</version> <configuration> <!-- hard code previous version; can't detect when running stateless on build server --> <oldVersion> From 2c8b57892849485ced7b946221b1eb3194e03529 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Jul 2021 22:45:28 +1000 Subject: [PATCH 578/774] Bump maven-source-plugin from 3.0.1 to 3.2.1 (#1587) Bumps [maven-source-plugin](https://github.com/apache/maven-source-plugin) from 3.0.1 to 3.2.1. - [Release notes](https://github.com/apache/maven-source-plugin/releases) - [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-3.0.1...maven-source-plugin-3.2.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-source-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2730ee2df4..cfbc6b2dd4 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> - <version>3.0.1</version> + <version>3.2.1</version> <configuration> <excludes> <exclude>org/jsoup/examples/**</exclude> From e10b0ee038e3c36a906c75e74f1b491c074a531b Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Fri, 16 Jul 2021 01:29:41 +0000 Subject: [PATCH 579/774] Dependabot ignore names fixed. (#1591) --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7761dccad6..cdc0543d19 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ updates: interval: weekly ignore: # Jetty 9.x needed for JDK8 compatibility; it still receives security updates - - dependency-name: "jetty-server" + - dependency-name: "org.eclipse.jetty:jetty-server" update-types: ["version-update:semver-major"] - - dependency-name: "jetty-servlet" + - dependency-name: "org.eclipse.jetty:jetty-servlet" update-types: ["version-update:semver-major"] From 4fa7c82af8ca51d4af9477a667441fe8153025a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jul 2021 11:35:51 +1000 Subject: [PATCH 580/774] Bump jetty-server from 9.4.42.v20210604 to 9.4.43.v20210629 (#1592) Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.42.v20210604 to 9.4.43.v20210629. - [Release notes](https://github.com/eclipse/jetty.project/releases) - [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.42.v20210604...jetty-9.4.43.v20210629) --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cfbc6b2dd4..92ae950704 100644 --- a/pom.xml +++ b/pom.xml @@ -324,7 +324,7 @@ <!-- jetty for webserver integration tests. 9.x is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.42.v20210604</version> + <version>9.4.43.v20210629</version> <scope>test</scope> </dependency> From b09511eb6510fd1ea70deaf55f60d0ccc4814c45 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 20 Jul 2021 15:15:12 +1000 Subject: [PATCH 581/774] Limit how far up the stack to scan for foster elements Prevents unbounded scan when supplied with crafted HTML Fixes #1593 --- CHANGES | 4 ++++ .../java/org/jsoup/parser/HtmlTreeBuilder.java | 4 +++- .../org/jsoup/integration/FuzzFixesTest.java | 14 ++++++++++++++ src/test/resources/fuzztests/1593.html.gz | Bin 0 -> 1126 bytes 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1593.html.gz diff --git a/CHANGES b/CHANGES index 262503eab4..856495f8ee 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,10 @@ jsoup changelog count per element when parsing to 512 (in real-world HTML, P99 is ~ 8). <https://github.com/jhy/jsoup/issues/1578> + * Bugfix [Fuzz]: Speed improvement for the foster formatting elements algo, by limiting how far up a crafted stack + to scan. + <https://github.com/jhy/jsoup/issues/1593> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index ed533b8d92..faddd6f8ea 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -336,7 +336,9 @@ private boolean isElementInQueue(ArrayList<Element> queue, Element element) { } Element getFromStack(String elName) { - for (int pos = stack.size() -1; pos >= 0; pos--) { + final int bottom = stack.size() - 1; + final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; + for (int pos = bottom; pos >= upper; pos--) { Element next = stack.get(pos); if (next.normalName().equals(elName)) { return next; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 122cb59d19..6f08c222f3 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -122,4 +122,18 @@ public void parseTimeout36150() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void parseTimeout1593() throws IOException { + // https://github.com/jhy/jsoup/issues/1593 + // had unbounded depth in the foster formatting element scan - now limited to <= 256 + // realworld HTML generally has only a few + File in = ParseTest.getFile("/fuzztests/1593.html.gz"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1593.html.gz b/src/test/resources/fuzztests/1593.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..8b8eab4b50937d1bb16011ddab47acfb9766275d GIT binary patch literal 1126 zcmb2|=HNIM_AP;d+tAd~STCa_H;3WvT}Qv*M3J_Ks|^dC9(8mYv++oaJ(w?b^YBay zVUvxWPRywc#%y{H;t3Irrwg2Q7Q`Lx<a`n{`}FsD#nrziO}Mb-esPE5xm#kVzZF(i z*Ok>2|NVCR`Q5v>XTR2;Umm}`yKr|#^8Vzn0Tu~P4vRJA3^EQW{<^Z~VZ+7-!_bJ^ zw($<C7c#ood1e)K*qP>AENEEyoO=a}l<&s|M`6B;%b)SGix_;ZVVsJhmucBN{RKi9 zCP$gsd@Wq&&oY+@IB?}NYY0>CvWf!>INM(Ye{N%5#`|QNatm`CGqNDpjH-PNUJEXY z@JDbSVoYq%U(D=$PCS6qWY#0bW;K~Zft#YY3tPMstIgU{;83K%cY?D<c)kaN`QP6v z%z1SY9|{~8gPGHptAAcSx9r2#Z1!;L+Vj``elPw1wWRj_;_6F(D|M!=uC%t<6|weL z`HlDW7rw=9+5GtCn?K);yC>&eYqkIPv0=Si#p`WV<zHSN{u4O2F=$^**_Ed;B^Q%T z-yGXrzw+&iFMm~e<(@8K;zJ1rAL$di?i}nq?4YnQl-QB7A>>2R&A_krZz>)(a55r$ z606UT95@?yZDRk5{ii-2W)x<^62iA1JbfGEg+C_#urz#62HNFt^z@<oTTcqqX59k? z$$4lH9)DX|^1#-4)zX)(%!r_tFnNfs5E#~r{O`^yIMCAI7HpGnp~d;j%F4rx$&AUX zHq>qU{Npd{6%;QKHtei4pGLx~pG;bf$Of|dpr^2eTMl`aP8JCkIFj8|1sMUhA4XON zPta4{69<_I!avmPCNkvxn(o7P=g)=*4;z@4v7Phte->{2_CfY_zIA24=B@qx{`J4t zFMr*0|Ni8i@fNYuSNF)u#jXkaYyIZCeMYtZt(zZn%KrVHdDPSV^&xqA=^x*nCmgm; zW|$g%cy<5lK5@Po0vF6G4bT2x4$MREw>;cm^`cXL#~I<a%-twiBhP;jWhto#`j6_f KC)=<yG5`Q=N)LGe literal 0 HcmV?d00001 From cd90f3f437e4963fb7d3e014c03638cac071ac49 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 23 Jul 2021 14:25:50 +1000 Subject: [PATCH 582/774] Bypass ownerdocument scan when setting attribute from tree builder Fixes #1595 --- CHANGES | 3 +++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 3 ++- .../java/org/jsoup/integration/FuzzFixesTest.java | 13 +++++++++++++ src/test/resources/fuzztests/1595.html.gz | Bin 0 -> 668 bytes 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1595.html.gz diff --git a/CHANGES b/CHANGES index 856495f8ee..4ad3f5edb4 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,9 @@ jsoup changelog to scan. <https://github.com/jhy/jsoup/issues/1593> + * Bugfix [Fuzz]: Speed improvement when parsing crafted HTML when transferring form attributes. + <https://github.com/jhy/jsoup/issues/1595> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 8a0f77efd5..2eb0261f84 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -460,7 +460,8 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.processStartTag("form"); if (startTag.hasAttribute("action")) { Element form = tb.getFormElement(); - form.attr("action", startTag.attributes.get("action")); + String action = startTag.attributes.get("action"); + form.attributes().put("action", action); // always LC, so don't need to scan up for ownerdoc } tb.processStartTag("hr"); tb.processStartTag("label"); diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 6f08c222f3..97504b3974 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -136,4 +136,17 @@ public void parseTimeout1593() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void parseTimeout1595() throws IOException { + // https://github.com/jhy/jsoup/issues/1595 + // Time was getting soaked when setting a form attribute by searching up the node.root for ownerdocuments + File in = ParseTest.getFile("/fuzztests/1595.html"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1595.html.gz b/src/test/resources/fuzztests/1595.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..db70068637689b901fa4c5587365282980734de8 GIT binary patch literal 668 zcmV;N0%QFjiwFo#Hu_)y3o$i0H7;m$ZEOJ5R?Th`F%Wi<3PK_+Pk_=QDkOfk(4wkk zFdVpW0S;U_c-N*`v01yZ(<VZqSB?nI@FJWl`T$&zkoqd1#a?H--gGycv`G=8mB*eR zkL`Rj86e<8Hv$j<T_8M;IH8PEqKK9f@cboUy@u+uhUY)-ut%+sQWj$fWf)Sc;j!Y% z=d|Ai6{?6~%RKST6X1zTzv><BDz+<8FmZ)fi$X}lW#8Ul@*tBWNdimsMPQs4&=uL2 zidjw3QM-Kn{^L`&EN*+-jMk~w7TyN3WaijNnjU_r`ST{w5tpd+9k}Y4JNohGfb0Vh z&FtVw4Ej^Vj~HZJ9)577((-G1Vs??Wsv8wXt}TrCC5GS5eRR8J0O~1h^2o4Yh@}%2 zD06kmq6ucu>fj2CVZnRH=+eKV!by3&%cE|a1uYV9v$Cfdu)P5tOCcaag*?WY6+)|< zs~c+wT}H@p$CeHb%(!?FPm>T>dSxPM7|@%B5;Ig`zHwgwu1IMM8iSS`<|VND<1T7S z!vrX;Ee&fnVRq6qS-HNw^2DZNo}SsMG4?_=cLC*O_iE42FW-?m$}<do9f_xrnhUu$ z*-u=}-CV;@`W3ZP@;vo^N)|n{qH#}xTi0HT=B=hjOW*%gbry~cf$R!=G$Yd0*f~=< zh|;edi7sgHqCR#JIp<r=<(6B~>oI-ae>qaHC<<CEcsu3+tCMa#n($6j|5H8V4Sy%c z)3L&>j|>N>?mj0Go>l#?Up{Za|J+BW!rfUkl4mP$wSDCD`?6XXl~?r~SmQT#GwnV| zkoHKmRQ`%?xeldkp5=?4XB`copT7<@?tO^43_9;({-#4%&pm1Ka{Vv%r6$H{4FCYa C#X|J} literal 0 HcmV?d00001 From adbfd71cfbe2fe2eb7a8b986a57676d0eb76e546 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 23 Jul 2021 15:33:06 +1000 Subject: [PATCH 583/774] Speed improvement when the stack was thousands of items deep, and non-matching close tags sent. Fixes #1596 --- CHANGES | 3 +++ .../java/org/jsoup/parser/HtmlTreeBuilder.java | 1 + .../org/jsoup/parser/HtmlTreeBuilderState.java | 8 ++++++++ .../org/jsoup/integration/FuzzFixesTest.java | 12 +++++++++++- src/test/resources/fuzztests/1596.html.gz | Bin 0 -> 3712 bytes 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1596.html.gz diff --git a/CHANGES b/CHANGES index 4ad3f5edb4..6b70a7f72f 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,9 @@ jsoup changelog * Bugfix [Fuzz]: Speed improvement when parsing crafted HTML when transferring form attributes. <https://github.com/jhy/jsoup/issues/1595> + * Bugfix [Fuzz]: Speed improvement when the stack was thousands of items deep, and non-matching close tags sent. + <https://github.com/jhy/jsoup/issues/1596> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index faddd6f8ea..40419f7a67 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -335,6 +335,7 @@ private boolean isElementInQueue(ArrayList<Element> queue, Element element) { return false; } + @Nullable Element getFromStack(String elName) { final int bottom = stack.size() - 1; final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 2eb0261f84..fcef2bcc3d 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -762,6 +762,14 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { final String name = t.asEndTag().normalName; // case insensitive search - goal is to preserve output case, not for the parse to be case sensitive final ArrayList<Element> stack = tb.getStack(); + + // deviate from spec slightly to speed when super deeply nested + Element elFromStack = tb.getFromStack(name); + if (elFromStack == null) { + tb.error(this); + return false; + } + for (int pos = stack.size() - 1; pos >= 0; pos--) { Element node = stack.get(pos); if (node.normalName().equals(name)) { diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 97504b3974..7b65100ddf 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -141,7 +141,7 @@ public void parseTimeout1593() throws IOException { public void parseTimeout1595() throws IOException { // https://github.com/jhy/jsoup/issues/1595 // Time was getting soaked when setting a form attribute by searching up the node.root for ownerdocuments - File in = ParseTest.getFile("/fuzztests/1595.html"); + File in = ParseTest.getFile("/fuzztests/1595.html.gz"); Document doc = Jsoup.parse(in, "UTF-8"); assertNotNull(doc); @@ -149,4 +149,14 @@ public void parseTimeout1595() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void parseTimeout1596() throws IOException { + // https://github.com/jhy/jsoup/issues/1596 + // Timesink when the stack was thousands of items deep, and non-matching close tags sent + File in = ParseTest.getFile("/fuzztests/1596.html.gz"); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1596.html.gz b/src/test/resources/fuzztests/1596.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e32443802048dc989bab5f735a66defd125ecbb9 GIT binary patch literal 3712 zcmb2|=HOs*{guGLZD?v~rk7EYo5S$-s%_ugbjG&C^^K|)LZU%7kLMR2Vmu<XiK$&h z&im)aBZkXVnwqA~YHJeRz#$c7Wwp&g-pII9*L~YTzn&udN3Wh`oV)YBeBX2H>vwjQ z)qT2r`24&2k3ZC}+gr`MZ!Oc;{`lp?58pl({P@YQ7hhBG_tn*pd$a%B+1Gvg`0&r4 zdrwy%=D+{z>&gE2G5a1D?D==|@8f3n`F1rWHgT+ZF@I0~J^c9a!#|I<FAr}#ee37k z?pyBWdQsQ;_S^pb{c-i>d2eI)?fCrV@A-NEHtqlMifi7Ux3{*cAD8cYzwd{W-n}nh z)%RGv{<QqFdh2xA;%`r8)c^S@?H=>*%Y6OEJ3r6g_xNOO-IqJO)xYLVPG9%=_PxpZ zF?)9ZP<!{NSp5H_<9}P*_k2B=zQ5wj$@ckj@eeoe`%ty-@z3=Zf9m%>&fouL$*0nP z|N8T5CZ0H6)2n~r@GJ2L53iqm|LLvmp6^_XWaVY<b7ZXlKKIYn{~r{8T+Fs%Q8&+z z|M%yg{{6qt^XtC<6z`8Oxc9Q#?nJzP)%!W~?(L~8t9txw?(XgRbLZZzd%mc2&-Z`F z<?X_7fPeqB?(f@NarG2daDICJll29QYCipq{k-=5_q;!^{pbHz-Sg<>$+erK%k$^m zwXOZj{O?=)Quhy2PZvmPA6DI^6!TzHT$^`^>y87ddwHh6=qhMDT_ve^QS>3x^sh!S zi?k21>b*|g;S%1$9dqq;LFYOS@f~ZXKNN};)GLV8A2nb!3`WzzXkHjC4~BV}m>>K- zph}~df9d~<e~&}o2dx;bDn_fz(I&%avuU*HINE&1=t5NTP1k2Mj?6uN^EMj;00mF^ AMgRZ+ literal 0 HcmV?d00001 From fb15b71b11b2a8f00a2a52e7df83429b7c0fa436 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 23 Jul 2021 15:35:26 +1000 Subject: [PATCH 584/774] Changelog typo --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6b70a7f72f..b8bf186f88 100644 --- a/CHANGES +++ b/CHANGES @@ -23,7 +23,7 @@ jsoup changelog * Bugfix [Fuzz]: the adoption agency algorithm can have an incorrect bookmark position <https://github.com/jhy/jsoup/issues/1576> - * Bugfiz [Fuzz]: malformed HTML could result in null elements on stack + * Bugfix [Fuzz]: malformed HTML could result in null elements on stack <https://github.com/jhy/jsoup/issues/1579> * Bugfix [Fuzz]: malformed deeply nested table elements could create a stack overflow. From b4f4f2c900ec261f9dba18255c900d566fb390e5 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 4 Aug 2021 15:54:24 +1000 Subject: [PATCH 585/774] Use a string builder when attribute names need to be accumulated Fixes #1605 --- CHANGES | 4 + src/main/java/org/jsoup/parser/Token.java | 117 +++++++++++------- .../org/jsoup/integration/FuzzFixesTest.java | 12 ++ src/test/resources/fuzztests/1605.html.gz | Bin 0 -> 4977 bytes 4 files changed, 89 insertions(+), 44 deletions(-) create mode 100644 src/test/resources/fuzztests/1605.html.gz diff --git a/CHANGES b/CHANGES index b8bf186f88..f103efe76e 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,10 @@ jsoup changelog * Bugfix [Fuzz]: Speed improvement when the stack was thousands of items deep, and non-matching close tags sent. <https://github.com/jhy/jsoup/issues/1596> + * Bugfix [Fuzz]: Speed improvement when an attribute name is 600K of quote characters or otherwise needs accumulation + vs being able to read in one hit. + <https://github.com/jhy/jsoup/issues/1605> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index d1b38b91e1..3c5bf100b2 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -3,6 +3,8 @@ import org.jsoup.helper.Validate; import org.jsoup.nodes.Attributes; +import javax.annotation.Nullable; + import static org.jsoup.internal.Normalizer.lowerCase; /** @@ -73,25 +75,32 @@ public boolean isForceQuirks() { } static abstract class Tag extends Token { - protected String tagName; - protected String normalName; // lc version of tag name, for case insensitive tree build - private String pendingAttributeName; // attribute names are generally caught in one hop, not accumulated - private StringBuilder pendingAttributeValue = new StringBuilder(); // but values are accumulated, from e.g. & in hrefs - private String pendingAttributeValueS; // try to get attr vals in one shot, vs Builder - private boolean hasEmptyAttributeValue = false; // distinguish boolean attribute from empty string value - private boolean hasPendingAttributeValue = false; + @Nullable protected String tagName; + @Nullable protected String normalName; // lc version of tag name, for case insensitive tree build + + private final StringBuilder attrName = new StringBuilder(); // try to get attr names and vals in one shot, vs Builder + @Nullable private String attrNameS; + private boolean hasAttrName = false; + + private final StringBuilder attrValue = new StringBuilder(); + @Nullable private String attrValueS; + private boolean hasAttrValue = false; + private boolean hasEmptyAttrValue = false; // distinguish boolean attribute from empty string value + boolean selfClosing = false; - Attributes attributes; // start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used). + @Nullable Attributes attributes; // start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used). @Override Tag reset() { tagName = null; normalName = null; - pendingAttributeName = null; - reset(pendingAttributeValue); - pendingAttributeValueS = null; - hasEmptyAttributeValue = false; - hasPendingAttributeValue = false; + reset(attrName); + attrNameS = null; + hasAttrName = false; + reset(attrValue); + attrValueS = null; + hasEmptyAttrValue = false; + hasAttrValue = false; selfClosing = false; attributes = null; return this; @@ -106,26 +115,30 @@ final void newAttribute() { if (attributes == null) attributes = new Attributes(); - if (pendingAttributeName != null && attributes.size() < MaxAttributes) { + if (hasAttrName && attributes.size() < MaxAttributes) { // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here - pendingAttributeName = pendingAttributeName.trim(); - if (pendingAttributeName.length() > 0) { + String name = attrName.length() > 0 ? attrName.toString() : attrNameS; + name = name.trim(); + if (name.length() > 0) { String value; - if (hasPendingAttributeValue) - value = pendingAttributeValue.length() > 0 ? pendingAttributeValue.toString() : pendingAttributeValueS; - else if (hasEmptyAttributeValue) + if (hasAttrValue) + value = attrValue.length() > 0 ? attrValue.toString() : attrValueS; + else if (hasEmptyAttrValue) value = ""; else value = null; // note that we add, not put. So that the first is kept, and rest are deduped, once in a context where case sensitivity is known (the appropriate tree builder). - attributes.add(pendingAttributeName, value); + attributes.add(name, value); } } - pendingAttributeName = null; - hasEmptyAttributeValue = false; - hasPendingAttributeValue = false; - reset(pendingAttributeValue); - pendingAttributeValueS = null; + reset(attrName); + attrNameS = null; + hasAttrName = false; + + reset(attrValue); + attrValueS = null; + hasAttrValue = false; + hasEmptyAttrValue = false; } final boolean hasAttributes() { @@ -138,7 +151,7 @@ final boolean hasAttribute(String key) { final void finaliseTag() { // finalises for emit - if (pendingAttributeName != null) { + if (hasAttrName) { newAttribute(); } } @@ -183,49 +196,65 @@ final void appendTagName(char append) { final void appendAttributeName(String append) { // might have null chars because we eat in one pass - need to replace with null replacement character append = append.replace(TokeniserState.nullChar, Tokeniser.replacementChar); - pendingAttributeName = pendingAttributeName == null ? append : pendingAttributeName.concat(append); + + ensureAttrName(); + if (attrName.length() == 0) { + attrNameS = append; + } else { + attrName.append(append); + } } final void appendAttributeName(char append) { - appendAttributeName(String.valueOf(append)); + ensureAttrName(); + attrName.append(append); } final void appendAttributeValue(String append) { - ensureAttributeValue(); - if (pendingAttributeValue.length() == 0) { - pendingAttributeValueS = append; + ensureAttrValue(); + if (attrValue.length() == 0) { + attrValueS = append; } else { - pendingAttributeValue.append(append); + attrValue.append(append); } } final void appendAttributeValue(char append) { - ensureAttributeValue(); - pendingAttributeValue.append(append); + ensureAttrValue(); + attrValue.append(append); } final void appendAttributeValue(char[] append) { - ensureAttributeValue(); - pendingAttributeValue.append(append); + ensureAttrValue(); + attrValue.append(append); } final void appendAttributeValue(int[] appendCodepoints) { - ensureAttributeValue(); + ensureAttrValue(); for (int codepoint : appendCodepoints) { - pendingAttributeValue.appendCodePoint(codepoint); + attrValue.appendCodePoint(codepoint); } } final void setEmptyAttributeValue() { - hasEmptyAttributeValue = true; + hasEmptyAttrValue = true; + } + + private void ensureAttrName() { + hasAttrName = true; + // if on second hit, we'll need to move to the builder + if (attrNameS != null) { + attrName.append(attrNameS); + attrNameS = null; + } } - private void ensureAttributeValue() { - hasPendingAttributeValue = true; + private void ensureAttrValue() { + hasAttrValue = true; // if on second hit, we'll need to move to the builder - if (pendingAttributeValueS != null) { - pendingAttributeValue.append(pendingAttributeValueS); - pendingAttributeValueS = null; + if (attrValueS != null) { + attrValue.append(attrValueS); + attrValueS = null; } } diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 7b65100ddf..a831fbb288 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -159,4 +159,16 @@ public void parseTimeout1596() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void parseTimeout1605() throws IOException { + // timesink with 600K of accumulating attribute name + File in = ParseTest.getFile("/fuzztests/1605.html.gz"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1605.html.gz b/src/test/resources/fuzztests/1605.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..a216775155c59a18c3d9151129c1cf2b8f5ddfc6 GIT binary patch literal 4977 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 VZ>*L_p3Pc7+4E7er_s!ye*!slx03(> literal 0 HcmV?d00001 From 436d119977bd44376e9dac92d82b21848a69732e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 4 Aug 2021 16:05:14 +1000 Subject: [PATCH 586/774] Limit stack depth when missing tags hit resetInsertionMode() Fixes #1606 --- CHANGES | 4 ++++ .../java/org/jsoup/parser/HtmlTreeBuilder.java | 5 ++++- .../org/jsoup/integration/FuzzFixesTest.java | 10 ++++++++++ src/test/resources/fuzztests/1606.html.gz | Bin 0 -> 1446 bytes 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1606.html.gz diff --git a/CHANGES b/CHANGES index f103efe76e..479e93130d 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,10 @@ jsoup changelog vs being able to read in one hit. <https://github.com/jhy/jsoup/issues/1605> + * Bugfix [Fuzz]: Speed improvement when closing missing empty tags (in XML comment processed as HTML) when thousands + deep in stack. + <https://github.com/jhy/jsoup/issues/1606> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 40419f7a67..43e2c394a6 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -443,7 +443,10 @@ private void replaceInQueue(ArrayList<Element> queue, Element out, Element in) { void resetInsertionMode() { // https://html.spec.whatwg.org/multipage/parsing.html#the-insertion-mode boolean last = false; - for (int pos = stack.size() -1; pos >= 0; pos--) { + final int bottom = stack.size() - 1; + final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; + + for (int pos = bottom; pos >= upper; pos--) { Element node = stack.get(pos); if (pos == 0) { last = true; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index a831fbb288..7c0fda17c6 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -171,4 +171,14 @@ public void parseTimeout1605() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void parseTimeout1606() throws IOException { + // https://github.com/jhy/jsoup/issues/1606 + // Timesink when closing missing empty tag (in XML comment processed as HTML) when thousands deep + File in = ParseTest.getFile("/fuzztests/1606.html.gz"); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1606.html.gz b/src/test/resources/fuzztests/1606.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0fd39d1e272729e387d490bad9d53776bc809f2 GIT binary patch literal 1446 zcmb2|=HNJ|&6UW&ZD?j-rk7EYo5S$-nq%H&2NAXhGL6a$yjHx@+R!WKbZ^s1$xA1+ zx;0mQ(Agoz{$f(`!4S<OxiP99nZLdEpP2S$`o`zmZSUXsm?;^nbKd^@YpJ_N`>ve+ z^f-O>k&M{6{M`{pRy_T2|E|}T)E)ceMDNw@*XiE$tLo#&Cx=fis|-)xC-tjnvw8f{ zJ$2o;jz{)xGn~_RD)+zt&&F%ppKhxx70uoM?P=ZZm&RMZuIW3sd42ow6T3D4IN!Tn z)meLd&bCk6Rx74$zBzk#*=||6OCL9^tBrpbckD3dx8=LV-{o(ZTW#`7|C#EqQu`~* z`EK{kvpcDLx23uL@R3%#w+enDt@ZzYKA%rMabB9PCJ6s3Pxk3ia}!qn|L600dn7{h zqH5P6WC1jJVz|$H$uQwR_eXyY#g+=^XD-t<xR6{#jE!JF$e;et`u~sp{{K4tr&d&Y zE<P50I2^-&I83X*rGZ0+*s!R-H);YX)+h)E`_=y!`!QMfZ<hID5MBEzao_&QTdXHN zkO@BZzeM+$O7W!pN!Oe70!1jxYR~QS@tC0gXa8ehK8{~o{~k|np6vFJg3JyoC_dB5 d1pEH~5pLTf>$j%<zJ0Bp`Q-m!y7P0H833196t(~W literal 0 HcmV?d00001 From 26d3c14ec3f0bf81c12e490efd7923e659696cca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 4 Aug 2021 17:00:38 +1000 Subject: [PATCH 587/774] Fixed an NPE hit on Current Element if there was nothing left on the stack And guarded other calls to Current Element when used in the same manner Fixes #1603 --- CHANGES | 5 +- .../jsoup/parser/HtmlTreeBuilderState.java | 49 ++++++++++--------- .../java/org/jsoup/parser/TreeBuilder.java | 13 ++++- .../java/org/jsoup/parser/HtmlParserTest.java | 7 +++ 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/CHANGES b/CHANGES index 479e93130d..18ac10cfb6 100644 --- a/CHANGES +++ b/CHANGES @@ -14,9 +14,12 @@ jsoup changelog * Bugfix: in the XML parser, XML processing instructions without attributes would be serialized as if they did. <https://github.com/jhy/jsoup/issues/770> - * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements + * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements. <https://github.com/jhy/jsoup/issues/1491> + * Bugfix: fixed an NPE when parsing fragment HTML into a table element. + <https://github.com/jhy/jsoup/issues/1603> + * Bugfix [Fuzz]: fixed a slow parse when a tag or an attribute name has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index fcef2bcc3d..f5b65ba770 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -580,7 +580,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { // static final String[] InBodyStartOptions = new String[]{"optgroup", "option"}; case "optgroup": case "option": - if (tb.currentElement().normalName().equals("option")) + if (tb.currentElementIs("option")) tb.processEndTag("option"); tb.reconstructFormattingElements(); tb.insert(startTag); @@ -590,7 +590,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { case "rt": if (tb.inScope("ruby")) { tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals("ruby")) { + if (!tb.currentElementIs("ruby")) { tb.error(this); tb.popStackToBefore("ruby"); // i.e. close up to but not include name } @@ -648,7 +648,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); } @@ -675,7 +675,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); // remove currentForm from stack. will shift anything under up. tb.removeFromStack(currentForm); @@ -688,7 +688,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return tb.process(endTag); } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); } @@ -700,7 +700,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); } @@ -716,7 +716,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(name); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(Constants.Headings); } @@ -736,7 +736,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); } @@ -747,7 +747,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); @@ -1000,7 +1000,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { } return true; // todo: as above todo } else if (t.isEOF()) { - if (tb.currentElement().normalName().equals("html")) + Element el = tb.currentElement(); + if (tb.currentElementIs("html")) tb.error(this); return true; // stops parsing } @@ -1059,7 +1060,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else { tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals("caption")) + if (!tb.currentElementIs("caption")) tb.error(this); tb.popStackToClose("caption"); tb.clearFormattingElementsToLastMarker(); @@ -1110,7 +1111,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { case EndTag: Token.EndTag endTag = t.asEndTag(); if (endTag.normalName.equals("colgroup")) { - if (tb.currentElement().normalName().equals("html")) { // frag case + if (tb.currentElementIs("html")) { // frag case tb.error(this); return false; } else { @@ -1121,7 +1122,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return anythingElse(t, tb); break; case EOF: - if (tb.currentElement().normalName().equals("html")) + if (tb.currentElementIs("html")) return true; // stop parsing; frag case else return anythingElse(t, tb); @@ -1277,7 +1278,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } tb.generateImpliedEndTags(); - if (!tb.currentElement().normalName().equals(name)) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); @@ -1344,13 +1345,13 @@ boolean process(Token t, HtmlTreeBuilder tb) { if (name.equals("html")) return tb.process(start, InBody); else if (name.equals("option")) { - if (tb.currentElement().normalName().equals("option")) + if (tb.currentElementIs("option")) tb.processEndTag("option"); tb.insert(start); } else if (name.equals("optgroup")) { - if (tb.currentElement().normalName().equals("option")) + if (tb.currentElementIs("option")) tb.processEndTag("option"); // pop option and flow to pop optgroup - if (tb.currentElement().normalName().equals("optgroup")) + if (tb.currentElementIs("optgroup")) tb.processEndTag("optgroup"); tb.insert(start); } else if (name.equals("select")) { @@ -1373,15 +1374,15 @@ else if (name.equals("option")) { name = end.normalName(); switch (name) { case "optgroup": - if (tb.currentElement().normalName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).normalName().equals("optgroup")) + if (tb.currentElementIs("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).normalName().equals("optgroup")) tb.processEndTag("option"); - if (tb.currentElement().normalName().equals("optgroup")) + if (tb.currentElementIs("optgroup")) tb.pop(); else tb.error(this); break; case "option": - if (tb.currentElement().normalName().equals("option")) + if (tb.currentElementIs("option")) tb.pop(); else tb.error(this); @@ -1400,7 +1401,7 @@ else if (name.equals("option")) { } break; case EOF: - if (!tb.currentElement().normalName().equals("html")) + if (!tb.currentElementIs("html")) tb.error(this); break; default: @@ -1487,17 +1488,17 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } } else if (t.isEndTag() && t.asEndTag().normalName().equals("frameset")) { - if (tb.currentElement().normalName().equals("html")) { // frag + if (tb.currentElementIs("html")) { // frag tb.error(this); return false; } else { tb.pop(); - if (!tb.isFragmentParsing() && !tb.currentElement().normalName().equals("frameset")) { + if (!tb.isFragmentParsing() && !tb.currentElementIs("frameset")) { tb.transition(AfterFrameset); } } } else if (t.isEOF()) { - if (!tb.currentElement().normalName().equals("html")) { + if (!tb.currentElementIs("html")) { tb.error(this); return true; } diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 1f37f3345d..6aa5b7999c 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -6,6 +6,7 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.io.Reader; import java.util.ArrayList; @@ -109,11 +110,21 @@ protected boolean processEndTag(String name) { } - protected Element currentElement() { + @Nullable protected Element currentElement() { int size = stack.size(); return size > 0 ? stack.get(size-1) : null; } + /** + Checks if the Current Element's normal name equals the supplied name. + @param normalName name to check + @return true if there is a current element on the stack, and its name equals the supplied + */ + protected boolean currentElementIs(String normalName) { + Element current = currentElement(); + return current != null && current.normalName().equals(normalName); + } + /** * If the parser is tracking errors, add an error at the current position. * @param msg error message diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 7f2ff97fa6..ae9e014bfa 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1424,4 +1424,11 @@ private boolean didAddElements(String input) { int xmlElementCount = xml.getAllElements().size(); return htmlElementCount > xmlElementCount; } + + @Test public void canSetHtmlOnCreatedTableElements() { + // https://github.com/jhy/jsoup/issues/1603 + Element element = new Element("tr"); + element.html("<tr><td>One</td></tr>"); + assertEquals("<tr>\n <tr>\n <td>One</td>\n </tr>\n</tr>", element.outerHtml()); + } } From dd2536b3f6e14411a67e9ef02a9305895fc224c1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 4 Aug 2021 17:26:01 +1000 Subject: [PATCH 588/774] Make CurrentElement not null; return root document if nothing on stack Fixes #1601 --- CHANGES | 5 ++++- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 6 ++---- .../java/org/jsoup/parser/HtmlTreeBuilderState.java | 3 +-- src/main/java/org/jsoup/parser/TreeBuilder.java | 11 +++++++++-- src/test/java/org/jsoup/parser/HtmlParserTest.java | 9 +++++++++ 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 18ac10cfb6..1717108966 100644 --- a/CHANGES +++ b/CHANGES @@ -17,9 +17,12 @@ jsoup changelog * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements. <https://github.com/jhy/jsoup/issues/1491> - * Bugfix: fixed an NPE when parsing fragment HTML into a table element. + * Bugfix: fixed an NPE when parsing fragment HTML into a standalone table element. <https://github.com/jhy/jsoup/issues/1603> + * Bugfix: fixed an NPE when parsing fragment heading HTML into a standalone p element. + <https://github.com/jhy/jsoup/issues/1601> + * Bugfix [Fuzz]: fixed a slow parse when a tag or an attribute name has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 43e2c394a6..8d9d8fa90b 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -274,9 +274,7 @@ void insert(Token.Comment commentToken) { void insert(Token.Character characterToken) { final Node node; - Element el = currentElement(); - if (el == null) - el = doc; // allows for whitespace to be inserted into the doc root object (not on the stack) + Element el = currentElement(); // will be doc if no current element; allows for whitespace to be inserted into the doc root object (not on the stack) final String tagName = el.normalName(); final String data = characterToken.getData(); @@ -604,7 +602,7 @@ List<String> getPendingTableCharacters() { process, then the UA must perform the above steps as if that element was not in the above list. */ void generateImpliedEndTags(String excludeTag) { - while ((excludeTag != null && !currentElement().normalName().equals(excludeTag)) && + while ((excludeTag != null && !currentElementIs(excludeTag)) && inSorted(currentElement().normalName(), TagSearchEndTags)) pop(); } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index f5b65ba770..d2e017dafe 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -774,7 +774,7 @@ boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { Element node = stack.get(pos); if (node.normalName().equals(name)) { tb.generateImpliedEndTags(name); - if (!name.equals(tb.currentElement().normalName())) + if (!tb.currentElementIs(name)) tb.error(this); tb.popStackToClose(name); break; @@ -1000,7 +1000,6 @@ boolean process(Token t, HtmlTreeBuilder tb) { } return true; // todo: as above todo } else if (t.isEOF()) { - Element el = tb.currentElement(); if (tb.currentElementIs("html")) tb.error(this); return true; // stops parsing diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 6aa5b7999c..42617f5201 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -110,9 +110,14 @@ protected boolean processEndTag(String name) { } - @Nullable protected Element currentElement() { + /** + Get the current element (last on the stack). If all items have been removed, returns the document instead + (which might not actually be on the stack; use stack.size() == 0 to test if required. + @return the last element on the stack, if any; or the root document + */ + protected Element currentElement() { int size = stack.size(); - return size > 0 ? stack.get(size-1) : null; + return size > 0 ? stack.get(size-1) : doc; } /** @@ -121,6 +126,8 @@ protected boolean processEndTag(String name) { @return true if there is a current element on the stack, and its name equals the supplied */ protected boolean currentElementIs(String normalName) { + if (stack.size() == 0) + return false; Element current = currentElement(); return current != null && current.normalName().equals(normalName); } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index ae9e014bfa..35749059e5 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1431,4 +1431,13 @@ private boolean didAddElements(String input) { element.html("<tr><td>One</td></tr>"); assertEquals("<tr>\n <tr>\n <td>One</td>\n </tr>\n</tr>", element.outerHtml()); } + + @Test public void parseFragmentOnCreatedDocument() { + String bareFragment = "<h2>text</h2>"; + List<Node> nodes = new Document("").parser().parseFragmentInput(bareFragment, new Element("p"), ""); + assertEquals(1, nodes.size()); + Node node = nodes.get(0); + assertEquals("h2", node.nodeName()); + assertEquals("<p><h2>text</h2></p>", node.parent().outerHtml()); + } } From d6a4d20ed67a03a51d801e2f8930ab0ae8be054d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 4 Aug 2021 17:56:45 +1000 Subject: [PATCH 589/774] In adoption agency, make sure there's sufficient elements on stack Fixes #1602 --- CHANGES | 3 +++ .../java/org/jsoup/parser/HtmlTreeBuilderState.java | 3 ++- src/test/java/org/jsoup/parser/HtmlParserTest.java | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1717108966..f57b60ab2a 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,9 @@ jsoup changelog * Bugfix: fixed an NPE when parsing fragment heading HTML into a standalone p element. <https://github.com/jhy/jsoup/issues/1601> + * Bugfix: fixed an IOOB when parsing a formatting fragment into a standalone p element. + <https://github.com/jhy/jsoup/issues/1602> + * Bugfix [Fuzz]: fixed a slow parse when a tag or an attribute name has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index d2e017dafe..09783a809f 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -816,7 +816,8 @@ else if (!tb.onStack(formatEl)) { // run-aways final int stackSize = stack.size(); int bookmark = -1; - for (int si = 0; si < stackSize && si < 64; si++) { + for (int si = 1; si < stackSize && si < 64; si++) { + // TODO: this no longer matches the current spec at https://html.spec.whatwg.org/#adoption-agency-algorithm and should be updated el = stack.get(si); if (el == formatEl) { commonAncestor = stack.get(si - 1); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 35749059e5..696d876651 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1433,6 +1433,7 @@ private boolean didAddElements(String input) { } @Test public void parseFragmentOnCreatedDocument() { + // https://github.com/jhy/jsoup/issues/1601 String bareFragment = "<h2>text</h2>"; List<Node> nodes = new Document("").parser().parseFragmentInput(bareFragment, new Element("p"), ""); assertEquals(1, nodes.size()); @@ -1440,4 +1441,13 @@ private boolean didAddElements(String input) { assertEquals("h2", node.nodeName()); assertEquals("<p><h2>text</h2></p>", node.parent().outerHtml()); } + + @Test public void nestedPFragments() { + // https://github.com/jhy/jsoup/issues/1602 + String bareFragment = "<p></p><a></a>"; + List<Node> nodes = new Document("").parser().parseFragmentInput(bareFragment, new Element("p"), ""); + assertEquals(2, nodes.size()); + Node node = nodes.get(0); + assertEquals("<p><p></p><a></a></p>", node.parent().outerHtml()); // mis-nested because fragment forced into the element, OK + } } From b4f20f07b81aa35ccf5432ed9be6f6049d9f5139 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 5 Aug 2021 18:44:48 +1000 Subject: [PATCH 590/774] Make sure resetInsertionMode breaks out to Body if nothing left on stack Fixes #1607 --- CHANGES | 3 +++ .../java/org/jsoup/parser/HtmlTreeBuilder.java | 4 ++++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 6 ++++-- .../org/jsoup/integration/FuzzFixesTest.java | 12 ++++++++++++ src/test/resources/fuzztests/1607.html.gz | Bin 0 -> 1457 bytes 5 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/fuzztests/1607.html.gz diff --git a/CHANGES b/CHANGES index f57b60ab2a..610b4fb733 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,9 @@ jsoup changelog deep in stack. <https://github.com/jhy/jsoup/issues/1606> + * Bugfix [Fuzz]: Fix a potential stack-overflow in the parser given crafted HTML, when the parser looped in the + InSelectInTable state. + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 8d9d8fa90b..4ac557e2e3 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -444,6 +444,10 @@ void resetInsertionMode() { final int bottom = stack.size() - 1; final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; + if (stack.size() == 0) { // nothing left of stack, just get to body + transition(HtmlTreeBuilderState.InBody); + } + for (int pos = bottom; pos >= upper; pos--) { Element node = stack.get(pos); if (pos == 0) { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 09783a809f..e932a4a88b 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1419,12 +1419,14 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isStartTag() && inSorted(t.asStartTag().normalName(), InSelecTableEnd)) { tb.error(this); - tb.processEndTag("select"); + tb.popStackToClose("select"); + tb.resetInsertionMode(); return tb.process(t); } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(),InSelecTableEnd )) { tb.error(this); if (tb.inTableScope(t.asEndTag().normalName())) { - tb.processEndTag("select"); + tb.popStackToClose("select"); + tb.resetInsertionMode(); return (tb.process(t)); } else return false; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 7c0fda17c6..c203b1f79c 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -181,4 +181,16 @@ public void parseTimeout1606() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void overflow1607() throws IOException { + // https://github.com/jhy/jsoup/issues/1607 + File in = ParseTest.getFile("/fuzztests/1607.html.gz"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1607.html.gz b/src/test/resources/fuzztests/1607.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..064f7adc2dac387fa604c45ef337fd3fd0ca4e2c GIT binary patch literal 1457 zcmV;i1y1@OiwFoBgbQH+3o|x2H8Cz|bZu+^?OIK2+(Z<1;0DcwL)7DLT0jVllM00> zyiB?z#~?099HI!k-f?#ouV-Y>Cc70yy&xeF70L}RjfA)$xX^1=P&k0#2t++0q#n2| ziUdar;mz1ydw-p55?Yj*WY^Envor6_Jb&}vym3MfRonB}R<-U;0SLS7(Sikzecu!{ z<`bm&2*SuKSNmDAi>MI@dGVmH6fO`5sYe}AV7~3w>bC5zCTO6DzUmHxkT4J&KLO#) z4eUA%shF|o-8;ZS8!X1fm1}qd6nGV{A?s$QHywgHkqjy4`&+H_h7490Ha05ESuC?k z3tkQI*P|O@p}eUEk`b^5r*8ZtN9ugx1&o$I>cB3C3)!a-G|DZ~-~Exh`);tNLj@V2 zDYsa*&zUh*9QmZ)5VUgb?#s8khj=eX+k!<v(5A2hY7e1js*@qCL9r6pSAg+n&wunN zEr2qI$Wq?#vKv%lgI9hF7{sO^XU47aUr@c|ZAh=200T(uoCJ)z0*Y~6KQK+BX_^Fz zCQ`|F(<B+b`ZDmkFNtQ%=jI#C5B9gf+E-$j%&*wOwm0pXC*N9&T+Y+mP8bu9M4Fvv z*d*JkP%l>X=Jtm-9LgF(p@1j0@tp=Qvcy_DE%)2s8VezrgyP!r%JOTawe>bMpbU*N z&O4Wc2R0O@u<v!t0IMoq@vMYxHE`z2CHw^=kf4dGD)}&gk4@AYo~UtDQ#J*b`Ft6` zVIIIml%RwXe7a7~fkoQwrFOWGQGT$lA-B5_^7WjO2c?T$AR4aHB}%{w=_;i&2{A|K z64GJ52$&aU9^x3QtJ$g3c|?HHdAuWq+4&cxrA?$ykhr{Bb&<NaO4ViO_RpVqVUG-Q zK$`BlvK;DH0E(=)UTh`taIVkdG$ZP{?NX<7izV?rq7M$T+EbgjShA`kcTUQ^>Rv!0 z<WpHs4hRxh=L1pw=7$-zzTvZ{u#)S}$IYpA<nK)TZq5&mTd+Ikm#NDF%I#3l{zb5_ zK=l&2anKnCw7Saf?%TT+oFM%F`g+fFjhLrXu$8>GXbAQl!tC{edKMPcyq^2D9mAg+ z*~y5mFCKNEhf0zw=x_=1J5Xk<YAFxFcNlL`{Nbh@kV!5a!j#*dI2at5hf82{iIA)} zF}}MzWO$F`V;|kdkNp})eU26;px&+4TSpUSJQ#bB@YP%??e47Ly0@}iQkT;5%CBF2 zA*p{3kVZyxC2nYpK2t6H-f7EfHha2w)jymXs_wAx$D6yKGrt~kR`W6LuCOhCB3_X9 z1_0SRds8P0W*IL<x;fES1AczO{?*N!B5{4Bi|~YD2;)!mjUIn*=lW-sVTI<U6E?~2 zrcMp9w);fR8L6>mF%C;3A3f`fXZ-M}C~tjwdO8X5$mrOwFeJ7bC*~@S8*}+aSBy#N zY}S=kA!jz?6fI5*FF}Df%3jSmCq3;VLUqhe`H4fCR_|zks?&G44Z0}sIniypjt8Cu zLw^~6;OLxMKs2aSUckP)zwTjBi8D^a+Et1coW?iL^aeO7bSz}XB884x6gIv4aT+!q zK<#(VCe-Fg=FO?HO&TE2cRWNF{CnDXF4{p+`zBAyjg&>X&!QM;5@r!8SyBF@c1p)D z4RNTQRJ~8|!1f6=>}%N9u&;`SeGU6(1@^P3)|=rtwvA(H^+2XdPJpe_8BpdBHk|Sx zgl#^SFk1s)7lcI%2{Z%i$ubShC@B+ntGts3a#;{l2`7^xO8I@QM|$ZT))~lQ`2+Ml z;gz&&3Sd+!^`UiMbcRD`I7Vr!j|^zI)kg;YM?RrGGN6wP=mquRT~N<J9&SLUO&=N1 zM+Qd2m_9O~j|}|xP^FIy=pzF<ccgPiu?MbmM>=<OM)nCb>}%N9u&;`SeGU6(1@`|1 Lc|XuMIXwUX=H|ps literal 0 HcmV?d00001 From 04735f9a5430cdaef175c65580a4c38d812945fc Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 11 Aug 2021 21:29:53 +1000 Subject: [PATCH 591/774] Tests that adoption agency algo doesn't IOOB Issue was fixed in 478b568061b317c79ba6fd018d9ac5fa931bdb02 Closes #1608 --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 696d876651..1979e55960 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1450,4 +1450,12 @@ private boolean didAddElements(String input) { Node node = nodes.get(0); assertEquals("<p><p></p><a></a></p>", node.parent().outerHtml()); // mis-nested because fragment forced into the element, OK } + + @Test public void nestedAnchorAdoption() { + // https://github.com/jhy/jsoup/issues/1608 + String html = "<a>\n<b>\n<div>\n<a>test</a>\n</div>\n</b>\n</a>"; + Document doc = Jsoup.parse(html); + assertNotNull(doc); + assertEquals("<a> <b> </b></a><b><div><a> </a><a>test</a> </div> </b>", TextUtil.stripNewlines(doc.body().html())); + } } From 2a4c9decd617e7892fe767a535803b68d2268dca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 12 Aug 2021 17:23:55 +1000 Subject: [PATCH 592/774] Escape ascii control codes in both XML and HTML Required for XML and easier to read for HTML Fixes #1556. --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Entities.java | 12 +++++++++--- src/test/java/org/jsoup/nodes/EntitiesTest.java | 13 +++++++++++++ src/test/java/org/jsoup/parser/HtmlParserTest.java | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 610b4fb733..688bfe8fff 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ jsoup changelog * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. <https://github.com/jhy/jsoup/pull/1536> + * Bugfix: when serializing output, escape characters that are in the < 0x20 range. This improves XML output + compatibility, and makes HTML output with these characters easier to read (as they're otherwise invisible). + <https://github.com/jhy/jsoup/issues/1556> + * Bugfix: the *|el wildcard namespace selector now also matches elements with no namespace. <https://github.com/jhy/jsoup/issues/1565> diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index caa78e2932..732bf34d1e 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -220,11 +220,17 @@ static void escape(Appendable accum, String string, OutputSettings out, else accum.append(c); break; + // we escape ascii control <x20 (other than tab, line-feed, carriage return) for XML compliance (required) and HTML ease of reading (not required) - https://www.w3.org/TR/xml/#charsets + case 0x9: + case 0xA: + case 0xD: + accum.append(c); + break; default: - if (canEncode(coreCharset, c, encoder)) - accum.append(c); - else + if (c < 0x20 || !canEncode(coreCharset, c, encoder)) appendEncoded(accum, escapeMode, codePoint); + else + accum.append(c); } } else { final String c = new String(Character.toChars(codePoint)); diff --git a/src/test/java/org/jsoup/nodes/EntitiesTest.java b/src/test/java/org/jsoup/nodes/EntitiesTest.java index a853f2125b..886dfca525 100644 --- a/src/test/java/org/jsoup/nodes/EntitiesTest.java +++ b/src/test/java/org/jsoup/nodes/EntitiesTest.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; +import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; import static org.jsoup.nodes.Document.OutputSettings; @@ -150,4 +151,16 @@ public class EntitiesTest { doc.outputSettings().escapeMode(xhtml); assertEquals("<a title=\"&lt;p>One&lt;/p>\">One</a>", element.outerHtml()); } + + @Test public void controlCharactersAreEscaped() { + // https://github.com/jhy/jsoup/issues/1556 + // we escape ascii control characters in both HTML and XML for compatibility. Required in XML and probably + // easier to read in HTML + String input = "<a foo=\"&#x1b;esc&#x7;bell\">Text &#x1b; &#x7;</a>"; + Document doc = Jsoup.parse(input); + assertEquals(input, doc.body().html()); + + Document xml = Jsoup.parse(input, "", Parser.xmlParser()); + assertEquals(input, xml.html()); + } } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 1979e55960..c641cc30ff 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -814,7 +814,7 @@ public class HtmlParserTest { @Test public void handlesNullInData() { Document doc = Jsoup.parse("<p id=\u0000>Blah \u0000</p>"); - assertEquals("<p id=\"\uFFFD\">Blah \u0000</p>", doc.body().html()); // replaced in attr, NOT replaced in data + assertEquals("<p id=\"\uFFFD\">Blah &#x0;</p>", doc.body().html()); // replaced in attr, NOT replaced in data (but is escaped as control char <0x20) } @Test public void handlesNullInComments() { From bdfe70da8533edd27a8a5e2357a5d3dd7a1d7f11 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 12 Aug 2021 18:55:49 +1000 Subject: [PATCH 593/774] Tests for Attribute and Attributes consistency Closes #1026 --- src/main/java/org/jsoup/nodes/Attribute.java | 1 + src/main/java/org/jsoup/nodes/Attributes.java | 2 +- .../java/org/jsoup/nodes/AttributesTest.java | 36 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index fa1331a4ee..1991847ffc 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -171,6 +171,7 @@ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { return shouldCollapseAttribute(key, val, out); } + // collapse unknown foo=null, known checked=null, checked="", checked=checked; write out others protected static boolean shouldCollapseAttribute(final String key, @Nullable final String val, final Document.OutputSettings out) { return ( out.syntax() == Document.OutputSettings.Syntax.html && diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 6e0d789887..2301d81822 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -127,7 +127,7 @@ public Attributes add(String key, @Nullable String value) { * @param value attribute value (may be null, to set a boolean attribute) * @return these attributes, for chaining */ - public Attributes put(String key, String value) { + public Attributes put(String key, @Nullable String value) { Validate.notNull(key); int i = indexOfKey(key); if (i != NotFound) diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index d2f986b096..51bde98427 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -1,5 +1,6 @@ package org.jsoup.nodes; +import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; import java.util.Iterator; @@ -231,4 +232,39 @@ public void testBoolean() { assertEquals(4, a.size()); assertEquals(2, a.asList().size()); // excluded from lists } + + @Test public void testBooleans() { + // want unknown=null, and known like async=null, async="", and async=async to collapse + String html = "<a foo bar=\"\" async=async qux=qux defer=deferring ismap inert=\"\">"; + Element el = Jsoup.parse(html).selectFirst("a"); + assertEquals(" foo bar=\"\" async qux=\"qux\" defer=\"deferring\" ismap inert", el.attributes().html()); + + } + + @Test public void booleanNullAttributesConsistent() { + Attributes attributes = new Attributes(); + attributes.put("key", null); + Attribute attribute = attributes.iterator().next(); + + assertEquals("key", attribute.html()); + assertEquals(" key", attributes.html()); + } + + @Test public void booleanEmptyString() { + Attributes attributes = new Attributes(); + attributes.put("checked", ""); + Attribute attribute = attributes.iterator().next(); + + assertEquals("checked", attribute.html()); + assertEquals(" checked", attributes.html()); + } + + @Test public void booleanCaseInsensitive() { + Attributes attributes = new Attributes(); + attributes.put("checked", "CHECKED"); + Attribute attribute = attributes.iterator().next(); + + assertEquals("checked", attribute.html()); + assertEquals(" checked", attributes.html()); + } } From 7ec238bd7c42e9381ac744e97c5fa9a49d54de1b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 12 Aug 2021 19:10:16 +1000 Subject: [PATCH 594/774] Clarify doc that it's OK to set attribute value to null Also added testcase, and return the value from the parent only if it was there. Closes #1025 --- src/main/java/org/jsoup/nodes/Attribute.java | 10 ++++++---- src/test/java/org/jsoup/nodes/AttributeTest.java | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 1991847ffc..f31124c99a 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -91,15 +91,17 @@ public boolean hasDeclaredValue() { /** Set the attribute value. - @param val the new attribute value; must not be null + @param val the new attribute value; may be null (to set an enabled boolean attribute) + @return the previous value (if was null; an empty string) */ - public String setValue(String val) { + public String setValue(@Nullable String val) { String oldVal = this.val; if (parent != null) { - oldVal = parent.get(this.key); // trust the container more int i = parent.indexOfKey(this.key); - if (i != Attributes.NotFound) + if (i != Attributes.NotFound) { + oldVal = parent.get(this.key); // trust the container more parent.vals[i] = val; + } } this.val = val; return Attributes.checkNotNull(oldVal); diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 0ee1618557..629ed8bf64 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -62,4 +62,14 @@ public void html() { assertFalse(a2.hasDeclaredValue()); assertTrue(a3.hasDeclaredValue()); } + + @Test public void canSetValueToNull() { + Attribute attr = new Attribute("one", "val"); + String oldVal = attr.setValue(null); + assertEquals("one", attr.html()); + assertEquals("val", oldVal); + + oldVal = attr.setValue("foo"); + assertEquals("", oldVal); // string, not null + } } From 0e1ca51dc8198f524e72ddf15710f9d9e834d499 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 12 Aug 2021 20:15:32 +1000 Subject: [PATCH 595/774] Enable adoption test --- src/test/java/org/jsoup/parser/HtmlParserTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index c641cc30ff..98718b47d4 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -586,7 +586,7 @@ public class HtmlParserTest { } @Test public void testHgroup() { - // jsoup used to not allow hroup in h{n}, but that's not in spec, and browsers are OK + // jsoup used to not allow hgroup in h{n}, but that's not in spec, and browsers are OK Document doc = Jsoup.parse("<h1>Hello <h2>There <hgroup><h1>Another<h2>headline</hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup>"); assertEquals("<h1>Hello </h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", TextUtil.stripNewlines(doc.body().html())); } @@ -651,10 +651,9 @@ public class HtmlParserTest { assertEquals("<b>1</b>\n<p><b>2</b>3</p>", doc.body().html()); } - @Disabled // todo: test case for https://github.com/jhy/jsoup/issues/845. Doesn't work yet. @Test public void handlesMisnestedAInDivs() { - String h = "<a href='#1'><div><div><a href='#2'>child</a</div</div></a>"; - String w = "<a href=\"#1\"></a><div><a href=\"#1\"></a><div><a href=\"#1\"></a><a href=\"#2\">child</a></div></div>"; + String h = "<a href='#1'><div><div><a href='#2'>child</a></div</div></a>"; + String w = "<a href=\"#1\"></a> <div> <a href=\"#1\"></a> <div> <a href=\"#1\"></a><a href=\"#2\">child</a> </div> </div>"; Document doc = Jsoup.parse(h); assertEquals( StringUtil.normaliseWhitespace(w), From e6b11b0bdd86e333fa0054c83760394f8fb76b1c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 12 Aug 2021 20:54:34 +1000 Subject: [PATCH 596/774] Make sure tags and related start with an ascii alpha, per spec Fixes #1006 --- CHANGES | 3 +++ .../org/jsoup/parser/CharacterReader.java | 11 ++++++++ .../java/org/jsoup/parser/TokeniserState.java | 22 +++++++-------- .../java/org/jsoup/parser/HtmlParserTest.java | 27 ++++++++++++++++--- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index 688bfe8fff..5f3b13b186 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,9 @@ jsoup changelog * Bugfix: fixed an IOOB when parsing a formatting fragment into a standalone p element. <https://github.com/jhy/jsoup/issues/1602> + * Bugfix: tag names must start with an ascii-alpha character. + <https://github.com/jhy/jsoup/issues/1006> + * Bugfix [Fuzz]: fixed a slow parse when a tag or an attribute name has thousands of null characters in it. <https://github.com/jhy/jsoup/issues/1580> diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index e229749460..ec80df96d8 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -514,6 +514,17 @@ boolean matchesLetter() { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c); } + /** + Checks if the current pos matches an ascii alpha (A-Z a-z) per https://infra.spec.whatwg.org/#ascii-alpha + @return if it matches or not + */ + boolean matchesAsciiAlpha() { + if (isEmpty()) + return false; + char c = charBuf[bufPos]; + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } + boolean matchesDigit() { if (isEmpty()) return false; diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 845201fb1f..f66c72e69d 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -109,7 +109,7 @@ void read(Tokeniser t, CharacterReader r) { t.advanceTransition(BogusComment); break; default: - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { t.createTagPending(true); t.transition(TagName); } else { @@ -127,7 +127,7 @@ void read(Tokeniser t, CharacterReader r) { t.eofError(this); t.emit("</"); t.transition(Data); - } else if (r.matchesLetter()) { + } else if (r.matchesAsciiAlpha()) { t.createTagPending(false); t.transition(TagName); } else if (r.matches('>')) { @@ -136,7 +136,7 @@ void read(Tokeniser t, CharacterReader r) { } else { t.error(this); t.createBogusCommentPending(); - t.advanceTransition(BogusComment); + t.transition(BogusComment); // reconsume char } } }, @@ -185,7 +185,7 @@ void read(Tokeniser t, CharacterReader r) { if (r.matches('/')) { t.createTempBuffer(); t.advanceTransition(RCDATAEndTagOpen); - } else if (r.matchesLetter() && t.appropriateEndTagName() != null && !r.containsIgnoreCase("</" + t.appropriateEndTagName())) { + } else if (r.matchesAsciiAlpha() && t.appropriateEndTagName() != null && !r.containsIgnoreCase("</" + t.appropriateEndTagName())) { // diverge from spec: got a start tag, but there's no appropriate end tag (</title>), so rather than // consuming to EOF; break out here t.tagPending = t.createTagPending(false).name(t.appropriateEndTagName()); @@ -199,7 +199,7 @@ void read(Tokeniser t, CharacterReader r) { }, RCDATAEndTagOpen { void read(Tokeniser t, CharacterReader r) { - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { t.createTagPending(false); t.tagPending.appendTagName(r.current()); t.dataBuffer.append(r.current()); @@ -212,7 +212,7 @@ void read(Tokeniser t, CharacterReader r) { }, RCDATAEndTagName { void read(Tokeniser t, CharacterReader r) { - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { String name = r.consumeLetterSequence(); t.tagPending.appendTagName(name); t.dataBuffer.append(name); @@ -419,7 +419,7 @@ void read(Tokeniser t, CharacterReader r) { }, ScriptDataEscapedLessthanSign { void read(Tokeniser t, CharacterReader r) { - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { t.createTempBuffer(); t.dataBuffer.append(r.current()); t.emit("<"); @@ -436,7 +436,7 @@ void read(Tokeniser t, CharacterReader r) { }, ScriptDataEscapedEndTagOpen { void read(Tokeniser t, CharacterReader r) { - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { t.createTagPending(false); t.tagPending.appendTagName(r.current()); t.dataBuffer.append(r.current()); @@ -925,7 +925,7 @@ void read(Tokeniser t, CharacterReader r) { } else if (r.matchConsumeIgnoreCase("DOCTYPE")) { t.transition(Doctype); } else if (r.matchConsume("[CDATA[")) { - // todo: should actually check current namepspace, and only non-html allows cdata. until namespace + // todo: should actually check current namespace, and only non-html allows cdata. until namespace // is implemented properly, keep handling as cdata //} else if (!t.currentNodeInHtmlNS() && r.matchConsume("[CDATA[")) { t.createTempBuffer(); @@ -1128,7 +1128,7 @@ void read(Tokeniser t, CharacterReader r) { }, BeforeDoctypeName { void read(Tokeniser t, CharacterReader r) { - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { t.createDoctypePending(); t.transition(DoctypeName); return; @@ -1708,7 +1708,7 @@ private static void readCharRef(Tokeniser t, TokeniserState advance) { } private static void readEndTag(Tokeniser t, CharacterReader r, TokeniserState a, TokeniserState b) { - if (r.matchesLetter()) { + if (r.matchesAsciiAlpha()) { t.createTagPending(false); t.transition(a); } else { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 98718b47d4..2d27e53114 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1042,11 +1042,11 @@ public void testInvalidTableContents() throws IOException { } @Test public void testSupportsNonAsciiTags() { - String body = "<進捗推移グラフ>Yes</進捗推移グラフ><русский-тэг>Correct</<русский-тэг>"; + String body = "<a進捗推移グラフ>Yes</a進捗推移グラフ><bрусский-тэг>Correct</<bрусский-тэг>"; Document doc = Jsoup.parse(body); - Elements els = doc.select("進捗推移グラフ"); + Elements els = doc.select("a進捗推移グラフ"); assertEquals("Yes", els.text()); - els = doc.select("русский-тэг"); + els = doc.select("bрусский-тэг"); assertEquals("Correct", els.text()); } @@ -1457,4 +1457,25 @@ private boolean didAddElements(String input) { assertNotNull(doc); assertEquals("<a> <b> </b></a><b><div><a> </a><a>test</a> </div> </b>", TextUtil.stripNewlines(doc.body().html())); } + + @Test public void tagsMustStartWithAscii() { + // https://github.com/jhy/jsoup/issues/1006 + String[] valid = {"a一", "a会员挂单金额5", "table(╯°□°)╯"}; + String[] invalid = {"一", "会员挂单金额5", "(╯°□°)╯"}; + + for (String tag : valid) { + Document doc = Jsoup.parse("<" + tag + ">Text</" + tag + ">"); + Elements els = doc.getElementsByTag(tag); + assertEquals(1, els.size()); + assertEquals(tag, els.get(0).tagName()); + assertEquals("Text", els.get(0).text()); + } + + for (String tag : invalid) { + Document doc = Jsoup.parse("<" + tag + ">Text</" + tag + ">"); + Elements els = doc.getElementsByTag(tag); + assertEquals(0, els.size()); + assertEquals("&lt;" + tag + "&gt;Text<!--/" + tag + "-->", doc.body().html()); + } + } } From 50ff7100ccf1c3c6e4b3d2de0e7771aff33ecc59 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 12:07:30 +1000 Subject: [PATCH 597/774] absUrl now supports already abs URLs, even when there's no defined stream handler Fixes #1610 --- CHANGES | 4 ++++ src/main/java/org/jsoup/internal/StringUtil.java | 4 +++- src/main/java/org/jsoup/nodes/Element.java | 2 +- src/test/java/org/jsoup/nodes/NodeTest.java | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 5f3b13b186..638ca4a369 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ jsoup changelog * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. <https://github.com/jhy/jsoup/pull/1536> + * Improvement: Element.absUrl() now supports tel: URLs, and other URLs that are already absolute but that Java does + not have input stream handlers for. + <https://github.com/jhy/jsoup/issues/1610> + * Bugfix: when serializing output, escape characters that are in the < 0x20 range. This improves XML output compatibility, and makes HTML output with these characters easier to read (as they're otherwise invisible). <https://github.com/jhy/jsoup/issues/1556> diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 53be1f9570..7917f80433 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -299,9 +299,11 @@ public static String resolve(final String baseUrl, final String relUrl) { } return resolve(base, relUrl).toExternalForm(); } catch (MalformedURLException e) { - return ""; + // it may still be valid, just that Java doesn't have a registered stream handler for it, e.g. tel: + return validUriScheme.matcher(relUrl).find() ? relUrl : ""; } } + private static final Pattern validUriScheme = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+-.]*:"); private static final ThreadLocal<Stack<StringBuilder>> threadLocalBuilders = new ThreadLocal<Stack<StringBuilder>>() { @Override diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 79839b68a8..0a0180bcc4 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -82,7 +82,7 @@ public Element(Tag tag, @Nullable String baseUri, @Nullable Attributes attribute * @param baseUri the base URI of this element. Optional, and will inherit from its parent, if any. * @see Tag#valueOf(String, ParseSettings) */ - public Element(Tag tag, String baseUri) { + public Element(Tag tag, @Nullable String baseUri) { this(tag, baseUri, null); } diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 1dd66f11ed..47d8fc7886 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -3,6 +3,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.parser.Tag; +import org.jsoup.select.Elements; import org.jsoup.select.NodeVisitor; import org.junit.jupiter.api.Test; @@ -132,6 +133,21 @@ public void handlesAbsOnProtocolessAbsoluteUris() { assertEquals("http://example.com/one/two.html", a1.absUrl("href")); } + @Test public void handlesAbsOnUnknownProtocols() { + // https://github.com/jhy/jsoup/issues/1610 + // URL would throw on unknown protocol tel: as no stream handler is registered + + String[] urls = {"mailto:example@example.com", "tel:867-5309"}; // mail has a handler, tel doesn't + for (String url : urls) { + Attributes attr = new Attributes().put("href", url); + Element noBase = new Element(Tag.valueOf("a"), null, attr); + assertEquals(url, noBase.absUrl("href")); + + Element withBase = new Element(Tag.valueOf("a"), "http://example.com/", attr); + assertEquals(url, withBase.absUrl("href")); + } + } + @Test public void testRemove() { Document doc = Jsoup.parse("<p>One <span>two</span> three</p>"); Element p = doc.select("p").first(); From 6b3ec64329f24f61f0eb806cef59f1fa5b233ca2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 12:11:09 +1000 Subject: [PATCH 598/774] Removed unused import --- src/test/java/org/jsoup/nodes/NodeTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 47d8fc7886..9dc1b2272d 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -3,7 +3,6 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; import org.jsoup.parser.Tag; -import org.jsoup.select.Elements; import org.jsoup.select.NodeVisitor; import org.junit.jupiter.api.Test; From a909600117d18af97cc432a15a1fa3c67bc100de Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 12:50:46 +1000 Subject: [PATCH 599/774] Comment on URL normalization --- src/main/java/org/jsoup/internal/StringUtil.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 7917f80433..f51fc2cfa9 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -288,8 +288,8 @@ public static URL resolve(URL base, String relUrl) throws MalformedURLException * @return an absolute URL if one was able to be generated, or the empty string if not */ public static String resolve(final String baseUrl, final String relUrl) { - URL base; try { + URL base; try { base = new URL(baseUrl); } catch (MalformedURLException e) { @@ -299,7 +299,8 @@ public static String resolve(final String baseUrl, final String relUrl) { } return resolve(base, relUrl).toExternalForm(); } catch (MalformedURLException e) { - // it may still be valid, just that Java doesn't have a registered stream handler for it, e.g. tel: + // it may still be valid, just that Java doesn't have a registered stream handler for it, e.g. tel + // we test here vs at start to normalize supported URLs (e.g. HTTP -> http) return validUriScheme.matcher(relUrl).find() ? relUrl : ""; } } From 9d538e634c47754f976e6503ed7e427f92802ec2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 13:03:45 +1000 Subject: [PATCH 600/774] Annotate some nullables --- src/main/java/org/jsoup/helper/DataUtil.java | 8 ++++---- src/main/java/org/jsoup/internal/StringUtil.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index a111a4e6ef..3b0a2b12ed 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -80,25 +80,25 @@ public static Document load(File in, @Nullable String charsetName, String baseUr /** * Parses a Document from an input steam. * @param in input stream to parse. The stream will be closed after reading. - * @param charsetName character set of input + * @param charsetName character set of input (optional) * @param baseUri base URI of document, to resolve relative links against * @return Document * @throws IOException on IO error */ - public static Document load(InputStream in, String charsetName, String baseUri) throws IOException { + public static Document load(InputStream in, @Nullable String charsetName, String baseUri) throws IOException { return parseInputStream(in, charsetName, baseUri, Parser.htmlParser()); } /** * Parses a Document from an input steam, using the provided Parser. * @param in input stream to parse. The stream will be closed after reading. - * @param charsetName character set of input + * @param charsetName character set of input (optional) * @param baseUri base URI of document, to resolve relative links against * @param parser alternate {@link Parser#xmlParser() parser} to use. * @return Document * @throws IOException on IO error */ - public static Document load(InputStream in, String charsetName, String baseUri, Parser parser) throws IOException { + public static Document load(InputStream in, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { return parseInputStream(in, charsetName, baseUri, parser); } diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index f51fc2cfa9..08352253fa 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -260,7 +260,7 @@ public static boolean isAscii(String string) { return true; } - private static Pattern extraDotSegmentsPattern = Pattern.compile("^/((\\.{1,2}/)+)"); + private static final Pattern extraDotSegmentsPattern = Pattern.compile("^/((\\.{1,2}/)+)"); /** * Create a new absolute URL, from a provided existing absolute URL and a relative URL component. * @param base the existing absolute base URL From eba3e39a0d4c6e55295b565511873f81a751dde2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 14:32:34 +1000 Subject: [PATCH 601/774] Fix an IOOB when HTML root cleared and then attributes added Fixes #1611 --- CHANGES | 3 +++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 15 +++++++++------ .../org/jsoup/integration/FuzzFixesTest.java | 12 ++++++++++++ src/test/resources/fuzztests/1611.html.gz | Bin 0 -> 817 bytes 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 src/test/resources/fuzztests/1611.html.gz diff --git a/CHANGES b/CHANGES index 638ca4a369..26522f4c70 100644 --- a/CHANGES +++ b/CHANGES @@ -74,6 +74,9 @@ jsoup changelog * Bugfix [Fuzz]: Fix a potential stack-overflow in the parser given crafted HTML, when the parser looped in the InSelectInTable state. + * Bugfix [Fuzz]: Fix an IOOB when the HTML root was cleared from the stack and then attributes were merged onto it. + <https://github.com/jhy/jsoup/issues/1611> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index e932a4a88b..7f63ea2dab 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -340,12 +340,15 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { break; case "html": tb.error(this); - // merge attributes onto real html - Element html = tb.getStack().get(0); - if (startTag.hasAttributes()) { - for (Attribute attribute : startTag.attributes) { - if (!html.hasAttr(attribute.getKey())) - html.attributes().put(attribute); + // merge attributes onto real html (if present) + stack = tb.getStack(); + if (stack.size() > 0) { + Element html = tb.getStack().get(0); + if (startTag.hasAttributes()) { + for (Attribute attribute : startTag.attributes) { + if (!html.hasAttr(attribute.getKey())) + html.attributes().put(attribute); + } } } break; diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index c203b1f79c..5d7bcc8576 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -193,4 +193,16 @@ public void overflow1607() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void oob() throws IOException { + // https://github.com/jhy/jsoup/issues/1611 + File in = ParseTest.getFile("/fuzztests/1611.html.gz"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1611.html.gz b/src/test/resources/fuzztests/1611.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..90215d0a84846de1b61ba9473cd61debd96f4a75 GIT binary patch literal 817 zcmV-11J3*(iwFq%L>FNI3o$k^F)nCyZEOJT7qM>BK-3jwV~F^Ipg=05APa#CQeRFo zL<l4%U?=DJTt~Iz)P7Og4k%p<3u`APnBWVlHc|i~u>cbz>cq~35DIs`xV4?wq@jgY z<emNAyXSZB-o3kf=PQ2Q+NtbzUKI-~6|MZRBIr%6QUv`v+=mkNscM~PCb>r)Hc!Ch zZ$LmL;xQ1+Yf@~+J_k=AxJw3%(4CL!hFYQE6oK%QeiprbRlxae;A-YAFhNC4w;5RI zaBQPaG!<I1v>>Z^@SDeHHmf15U4(N5rG~3brj*Y3t3EOZ47IuTSt$kGk^xp`Rb81B zu#PrM$UIP8P<1!f%P<xi{5AR#avWH;T6v_JR4}r-;@EApnZ8u#Ov`7c>(!Xywi}ra zo^&jthMxZE<*d%qqMJ4$>R1ypdfvvHkpBiJ_GfH2w;*kwmxd%C!^~h^=9aUQ1iFnS zkC`kkT@JZ78@-L8Htj^6r_jo?_3wO&_hp&MY2QKNpO&r|O&0wG3}qPWN~9v&zt0~0 zUMYahi>xR;L)7jh<DXs}WGhJC3ckA)h^~&=Z+2<d4YUHTZj9NcyAJ5h02+hjp66#4 z=QfAIEY3OQIgXVOvj`u>)p+&!2(8n-xy6tVtXhm35vyeD!{cpnipj%_6boN=K(M01 znH%;4;puLN0)-i<{v7vInI^4&ji*T~S*A&AnzRlIV%Q`cegF%Sphp|NO_9l4z`zv( zl148Fr1|F|*MMN&YJvvh6(D&&KxkT=p#y=I=bhIbx|k$SikobKv4tXImlO?<3inDt zd;C%(;u~1vCbV~DfGXe%@f{C8B7(pPj)(<dF3V0{Ki%u(HfUn8(TUgDkNyDyJBTQD zJ7I7~5^*O@S4eM{NzxCUZyW;enceM(`)g7qG<0FIo{w-)heD3Pv%Q@&-m$-(t>f6h z84}rz!!(OTc5tQ%FU?H#V0Pj}*KC%M+G<S>F7^Xh=Rb*$`mN(JgMV{v)sC%a6RvvQ vM%Q#}E&q@ftlN;;HRmJsFwJ1f^3CN&n$7XL=KzhB(WZX^efBe?;}ie@h?0w7 literal 0 HcmV?d00001 From 42da86439df7545a7418044f7e8804b7dfb2de15 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 15:31:35 +1000 Subject: [PATCH 602/774] In bogusComment, make sure unconsume not called after a potential buffer up Wasn't able to repro with the supplied test case, but could previously happen. Audited other uses of unconsume and all are immediately after consume, so safe to call (as there's no path to a bufferup) Fixes #1612 --- src/main/java/org/jsoup/parser/Tokeniser.java | 3 ++- .../java/org/jsoup/parser/TokeniserState.java | 11 +++++------ .../org/jsoup/integration/FuzzFixesTest.java | 15 +++++++++++++++ src/test/resources/fuzztests/1612.html.gz | Bin 0 -> 2376 bytes 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/fuzztests/1612.html.gz diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 0f08cae2a5..05a95c8cdb 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -4,6 +4,7 @@ import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Entities; +import javax.annotation.Nullable; import java.util.Arrays; /** @@ -152,7 +153,7 @@ void advanceTransition(TokeniserState state) { final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays final private int[] multipointHolder = new int[2]; - int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { + @Nullable int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { if (reader.isEmpty()) return null; if (additionalAllowedCharacter != null && additionalAllowedCharacter == reader.current()) diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index f66c72e69d..45b8aab6b6 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -106,7 +106,7 @@ void read(Tokeniser t, CharacterReader r) { break; case '?': t.createBogusCommentPending(); - t.advanceTransition(BogusComment); + t.transition(BogusComment); break; default: if (r.matchesAsciiAlpha()) { @@ -136,7 +136,8 @@ void read(Tokeniser t, CharacterReader r) { } else { t.error(this); t.createBogusCommentPending(); - t.transition(BogusComment); // reconsume char + t.commentPending.append('/'); // push the / back on that got us here + t.transition(BogusComment); } } }, @@ -906,11 +907,9 @@ void read(Tokeniser t, CharacterReader r) { BogusComment { void read(Tokeniser t, CharacterReader r) { // todo: handle bogus comment starting from eof. when does that trigger? - // rewind to capture character that lead us here - r.unconsume(); t.commentPending.append(r.consumeTo('>')); // todo: replace nullChar with replaceChar - char next = r.consume(); + char next = r.current(); if (next == '>' || next == eof) { t.emitCommentPending(); t.transition(Data); @@ -933,7 +932,7 @@ void read(Tokeniser t, CharacterReader r) { } else { t.error(this); t.createBogusCommentPending(); - t.advanceTransition(BogusComment); // advance so this character gets in bogus comment data's rewind + t.transition(BogusComment); } } }, diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 5d7bcc8576..de25210302 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -205,4 +205,19 @@ public void oob() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void unconsume() throws IOException { + // https://github.com/jhy/jsoup/issues/1612 + // I wasn't able to repro this with different ways of loading strings - think somehow the fuzzers input + // buffer is different and the bufferUp() happened at a different point. Regardless, did find an unsafe use + // of unconsume() after a buffer up in bogus comment, so cleaned that up. + File in = ParseTest.getFile("/fuzztests/1612.html.gz"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + + Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); + assertNotNull(docXml); + } } diff --git a/src/test/resources/fuzztests/1612.html.gz b/src/test/resources/fuzztests/1612.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..fa1742bab68eef1633abeb4e334885c686d5d4f7 GIT binary patch literal 2376 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 literal 0 HcmV?d00001 From 0dcb53a73cd11530caee713a46d95ac9867b2805 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 14 Aug 2021 17:53:15 +1000 Subject: [PATCH 603/774] Correctly consume to exit state --- src/main/java/org/jsoup/parser/TokeniserState.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 45b8aab6b6..2907ad59fc 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -911,6 +911,7 @@ void read(Tokeniser t, CharacterReader r) { // todo: replace nullChar with replaceChar char next = r.current(); if (next == '>' || next == eof) { + r.consume(); t.emitCommentPending(); t.transition(Data); } From d2c455c94a3aaaca29d8cec6bd53ee9824622b51 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 Aug 2021 12:31:12 +1000 Subject: [PATCH 604/774] Speed improvement: cap number of cloned active formatting elements Also saves on memory allocations for empty attribute lists. Fixes #1613 --- CHANGES | 5 +++++ src/main/java/org/jsoup/nodes/Attributes.java | 1 - src/main/java/org/jsoup/nodes/Node.java | 12 +++++++++++- .../java/org/jsoup/parser/HtmlTreeBuilder.java | 7 +++++-- .../org/jsoup/parser/HtmlTreeBuilderState.java | 3 +-- .../org/jsoup/integration/FuzzFixesTest.java | 9 +++++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 16 ++++++++++++++++ src/test/resources/fuzztests/1613.html.gz | Bin 0 -> 7088 bytes 8 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 src/test/resources/fuzztests/1613.html.gz diff --git a/CHANGES b/CHANGES index 26522f4c70..73707f2fd6 100644 --- a/CHANGES +++ b/CHANGES @@ -77,6 +77,11 @@ jsoup changelog * Bugfix [Fuzz]: Fix an IOOB when the HTML root was cleared from the stack and then attributes were merged onto it. <https://github.com/jhy/jsoup/issues/1611> + * Bugfix [Fuzz]: Improved the speed of parsing when crafted HTML contains hundreds of active formatting elements + that were copied for all new elements (similar to an amplification attack). The number of considered active + formatting elements that will be cloned when mis-nested is now capped to 12. + <https://github.com/jhy/jsoup/issues/1613> + *** Release 1.14.1 [2021-Jul-10] * Change: updated the minimum supported Java version from Java 7 to Java 8. diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 2301d81822..61d9cbd74a 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -275,7 +275,6 @@ public void addAll(Attributes incoming) { // todo - should this be case insensitive? put(attr); } - } public Iterator<Attribute> iterator() { diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index dd0a190c9e..37d2a59819 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -86,6 +86,16 @@ else if (attributeKey.startsWith("abs:")) */ public abstract Attributes attributes(); + /** + Get the number of attributes that this Node has. + @return the number of attributes + @since 1.14.2 + */ + public int attributesSize() { + // added so that we can test how many attributes exist without implicitly creating the Attributes object + return hasAttributes() ? attributes().size() : 0; + } + /** * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is * <b>case insensitive</b>. The key will be set with case sensitivity as set in the parser settings. @@ -100,7 +110,7 @@ public Node attr(String attributeKey, String attributeValue) { } /** - * Test if this element has an attribute. <b>Case insensitive</b> + * Test if this Node has an attribute. <b>Case insensitive</b>. * @param attributeKey The attribute key to check. * @return true if the attribute exists, false if not. */ diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 4ac557e2e3..b276858a83 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -690,10 +690,11 @@ void reconstructFormattingElements() { Element entry = last; int size = formattingElements.size(); + int ceil = size - maxUsedFormattingElements; if (ceil <0) ceil = 0; int pos = size - 1; boolean skip = false; while (true) { - if (pos == 0) { // step 4. if none before, skip to 8 + if (pos == ceil) { // step 4. if none before, skip to 8 skip = true; break; } @@ -710,7 +711,8 @@ void reconstructFormattingElements() { skip = false; // can only skip increment from 4. Element newEl = insertStartTag(entry.normalName()); // todo: avoid fostering here? // newEl.namespace(entry.namespace()); // todo: namespaces - newEl.attributes().addAll(entry.attributes()); + if (entry.attributesSize() > 0) + newEl.attributes().addAll(entry.attributes()); // 10. replace entry with new entry formattingElements.set(pos, newEl); @@ -720,6 +722,7 @@ void reconstructFormattingElements() { break; } } + private static final int maxUsedFormattingElements = 12; // limit how many elements get recreated void clearFormattingElementsToLastMarker() { while (!formattingElements.isEmpty()) { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 7f63ea2dab..dedcb001a6 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -815,8 +815,7 @@ else if (!tb.onStack(formatEl)) { Element furthestBlock = null; Element commonAncestor = null; boolean seenFormattingElement = false; - // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents - // run-aways + // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth) this prevents run-aways final int stackSize = stack.size(); int bookmark = -1; for (int si = 1; si < stackSize && si < 64; si++) { diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index de25210302..53a3bfd9b8 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -220,4 +220,13 @@ public void unconsume() throws IOException { Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); assertNotNull(docXml); } + + @Test + public void test36916() throws IOException { + // https://github.com/jhy/jsoup/issues/1613 + File in = ParseTest.getFile("/fuzztests/1613.html.gz"); + + Document doc = Jsoup.parse(in, "UTF-8"); + assertNotNull(doc); + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 996d016316..08a657deca 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2039,4 +2039,20 @@ public void childNodesAccessorDoesNotVivify() { els.add(new Element("a")); assertEquals(1, els.size()); } + + @Test public void attributeSizeDoesNotAutoVivify() { + Document doc = Jsoup.parse("<p></p>"); + Element p = doc.selectFirst("p"); + assertNotNull(p); + assertFalse(p.hasAttributes()); + assertEquals(0, p.attributesSize()); + assertFalse(p.hasAttributes()); + + p.attr("foo", "bar"); + assertEquals(1, p.attributesSize()); + assertTrue(p.hasAttributes()); + + p.removeAttr("foo"); + assertEquals(0, p.attributesSize()); + } } diff --git a/src/test/resources/fuzztests/1613.html.gz b/src/test/resources/fuzztests/1613.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..ee5eb319f62bd3c16ba2d9c472340e5034e6bcf0 GIT binary patch literal 7088 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 literal 0 HcmV?d00001 From 530c5b0fcbef330ea762071144a864e19b1c7595 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 Aug 2021 13:38:54 +1000 Subject: [PATCH 605/774] Refactored fuzz tests to iterate all files in directory; run timeout tests --- src/main/java/org/jsoup/Jsoup.java | 29 ++- src/main/java/org/jsoup/helper/DataUtil.java | 30 ++- .../org/jsoup/integration/FuzzFixesIT.java | 61 +++++ .../org/jsoup/integration/FuzzFixesTest.java | 213 ++---------------- src/test/java/org/jsoup/parser/ParserIT.java | 2 + src/test/resources/fuzztests/36192.html.gz | Bin 0 -> 7089 bytes 6 files changed, 132 insertions(+), 203 deletions(-) create mode 100644 src/test/java/org/jsoup/integration/FuzzFixesIT.java create mode 100644 src/test/resources/fuzztests/36192.html.gz diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index a8f1f44726..81b7b724b9 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -108,7 +108,7 @@ public static Connection newSession() { /** Parse the contents of a file as HTML. - @param in file to load HTML from + @param file file to load HTML from. Supports gzipped files (ending in .z or .gz). @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if present, or fall back to {@code UTF-8} (which is often safe to do). @param baseUri The URL where the HTML was retrieved from, to resolve relative links against. @@ -116,14 +116,14 @@ public static Connection newSession() { @throws IOException if the file could not be found, or read, or if the charsetName is invalid. */ - public static Document parse(File in, @Nullable String charsetName, String baseUri) throws IOException { - return DataUtil.load(in, charsetName, baseUri); + public static Document parse(File file, @Nullable String charsetName, String baseUri) throws IOException { + return DataUtil.load(file, charsetName, baseUri); } /** Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs. - @param in file to load HTML from + @param file file to load HTML from. Supports gzipped files (ending in .z or .gz). @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if present, or fall back to {@code UTF-8} (which is often safe to do). @return sane HTML @@ -131,8 +131,25 @@ public static Document parse(File in, @Nullable String charsetName, String baseU @throws IOException if the file could not be found, or read, or if the charsetName is invalid. @see #parse(File, String, String) */ - public static Document parse(File in, @Nullable String charsetName) throws IOException { - return DataUtil.load(in, charsetName, in.getAbsolutePath()); + public static Document parse(File file, @Nullable String charsetName) throws IOException { + return DataUtil.load(file, charsetName, file.getAbsolutePath()); + } + + /** + Parse the contents of a file as HTML. + + @param file file to load HTML from. Supports gzipped files (ending in .z or .gz). + @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if + present, or fall back to {@code UTF-8} (which is often safe to do). + @param baseUri The URL where the HTML was retrieved from, to resolve relative links against. + @param parser alternate {@link Parser#xmlParser() parser} to use. + @return sane HTML + + @throws IOException if the file could not be found, or read, or if the charsetName is invalid. + @since 1.14.2 + */ + public static Document parse(File file, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { + return DataUtil.load(file, charsetName, baseUri, parser); } /** diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 3b0a2b12ed..7100f52c58 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -49,20 +49,38 @@ public final class DataUtil { private DataUtil() {} + /** + * Loads and parses a file to a Document, with the HtmlParser. Files that are compressed with gzip (and end in {@code .gz} or {@code .z}) + * are supported in addition to uncompressed files. + * + * @param file file to load + * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in + * the file will always override this setting. + * @param baseUri base URI of document, to resolve relative links against + * @return Document + * @throws IOException on IO error + */ + public static Document load(File file, @Nullable String charsetName, String baseUri) throws IOException { + return load(file, charsetName, baseUri, Parser.htmlParser()); + } + /** * Loads and parses a file to a Document. Files that are compressed with gzip (and end in {@code .gz} or {@code .z}) * are supported in addition to uncompressed files. * - * @param in file to load + * @param file file to load * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in * the file will always override this setting. * @param baseUri base URI of document, to resolve relative links against + * @param parser alternate {@link Parser#xmlParser() parser} to use. + * @return Document * @throws IOException on IO error + * @since 1.14.2 */ - public static Document load(File in, @Nullable String charsetName, String baseUri) throws IOException { - InputStream stream = new FileInputStream(in); - String name = Normalizer.lowerCase(in.getName()); + public static Document load(File file, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { + InputStream stream = new FileInputStream(file); + String name = Normalizer.lowerCase(file.getName()); if (name.endsWith(".gz") || name.endsWith(".z")) { // unfortunately file input streams don't support marks (why not?), so we will close and reopen after read boolean zipped; @@ -72,9 +90,9 @@ public static Document load(File in, @Nullable String charsetName, String baseUr stream.close(); } - stream = zipped ? new GZIPInputStream(new FileInputStream(in)) : new FileInputStream(in); + stream = zipped ? new GZIPInputStream(new FileInputStream(file)) : new FileInputStream(file); } - return parseInputStream(stream, charsetName, baseUri, Parser.htmlParser()); + return parseInputStream(stream, charsetName, baseUri, parser); } /** diff --git a/src/test/java/org/jsoup/integration/FuzzFixesIT.java b/src/test/java/org/jsoup/integration/FuzzFixesIT.java new file mode 100644 index 0000000000..a060de561b --- /dev/null +++ b/src/test/java/org/jsoup/integration/FuzzFixesIT.java @@ -0,0 +1,61 @@ +package org.jsoup.integration; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.parser.Parser; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.File; +import java.io.IOException; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup As some of these + are timeout tests - run each file 100 times and ensure under time. + */ +public class FuzzFixesIT { + static int numIters = 50; + static int timeout = 20; // external fuzzer is set to 60 for 100 runs + static File testDir = ParseTest.getFile("/fuzztests/"); + + private static Stream<File> testFiles() { + File[] files = testDir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 10); + + return Stream.of(files); + } + + @ParameterizedTest + @MethodSource("testFiles") + void testHtmlParse(File file) throws IOException { + long startTime = System.currentTimeMillis(); + long completeBy = startTime + timeout * 1000L; + + for (int i = 0; i < numIters; i++) { + Document doc = Jsoup.parse(file, "UTF-8", "https://example.com/"); + assertNotNull(doc); + if (System.currentTimeMillis() > completeBy) + Assertions.fail(String.format("Timeout: only completed %d iters of [%s] in %d seconds", i, file.getName(), timeout)); + } + } + + @ParameterizedTest + @MethodSource("testFiles") + void testXmlParse(File file) throws IOException { + long startTime = System.currentTimeMillis(); + long completeBy = startTime + timeout * 1000L; + + for (int i = 0; i < numIters; i++) { + Document doc = Jsoup.parse(file, "UTF-8", "https://example.com/", Parser.xmlParser()); + assertNotNull(doc); + if (System.currentTimeMillis() > completeBy) + Assertions.fail(String.format("Timeout: only completed %d iters of [%s] in %d seconds", i, file.getName(), timeout)); + } + } +} diff --git a/src/test/java/org/jsoup/integration/FuzzFixesTest.java b/src/test/java/org/jsoup/integration/FuzzFixesTest.java index 53a3bfd9b8..0fd668f256 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesTest.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesTest.java @@ -4,18 +4,30 @@ import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** - Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup + Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup. Contains inline + string cases causing exceptions. Timeout tests are in FuzzFixesIT. */ public class FuzzFixesTest { + private static Stream<File> testFiles() { + File[] files = FuzzFixesIT.testDir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 10); + + return Stream.of(files); + } + @Test public void blankAbsAttr() { // https://github.com/jhy/jsoup/issues/1541 @@ -24,61 +36,6 @@ public void blankAbsAttr() { assertNotNull(doc); } - @Test - public void resetInsertionMode() throws IOException { - // https://github.com/jhy/jsoup/issues/1538 - File in = ParseTest.getFile("/fuzztests/1538.html.gz"); // lots of escape chars etc. - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - } - - @Test - public void xmlDeclOverflow() throws IOException { - // https://github.com/jhy/jsoup/issues/1539 - File in = ParseTest.getFile("/fuzztests/1539.html.gz"); // lots of escape chars etc. - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void xmlDeclOverflowOOM() throws IOException { - // https://github.com/jhy/jsoup/issues/1569 - File in = ParseTest.getFile("/fuzztests/1569.html.gz"); - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void stackOverflowState14() throws IOException { - // https://github.com/jhy/jsoup/issues/1543 - File in = ParseTest.getFile("/fuzztests/1543.html.gz"); - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - } - - @Test - public void parseTimeout() throws IOException { - // https://github.com/jhy/jsoup/issues/1544 - File in = ParseTest.getFile("/fuzztests/1544.html.gz"); - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - } - - @Test - public void parseTimeout1580() throws IOException { - // https://github.com/jhy/jsoup/issues/1580 - // a shedload of NULLs in append tagname so was spinning in there. Fixed to eat and replace all the chars in one hit - File in = ParseTest.getFile("/fuzztests/1580.html.gz"); - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - } - @Test public void bookmark() { // https://github.com/jhy/jsoup/issues/1576 @@ -90,143 +47,17 @@ public void bookmark() { assertNotNull(xmlDoc); } - @Test - public void scope1579() { - // https://github.com/jhy/jsoup/issues/1579 - String html = "<table<html\u001D<ÛÛ<tr><body\u001D<b:<select<m<input></html> </html>"; - Document doc = Jsoup.parse(html); - assertNotNull(doc); - - Document xmlDoc = Parser.xmlParser().parseInput(html, ""); - assertNotNull(xmlDoc); - } - - @Test - public void overflow1577() throws IOException { - // https://github.com/jhy/jsoup/issues/1577 - File in = ParseTest.getFile("/fuzztests/1577.html.gz"); - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void parseTimeout36150() throws IOException { - File in = ParseTest.getFile("/fuzztests/1580-attrname.html.gz"); - // pretty much 1MB of null chars in text head - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void parseTimeout1593() throws IOException { - // https://github.com/jhy/jsoup/issues/1593 - // had unbounded depth in the foster formatting element scan - now limited to <= 256 - // realworld HTML generally has only a few - File in = ParseTest.getFile("/fuzztests/1593.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void parseTimeout1595() throws IOException { - // https://github.com/jhy/jsoup/issues/1595 - // Time was getting soaked when setting a form attribute by searching up the node.root for ownerdocuments - File in = ParseTest.getFile("/fuzztests/1595.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); + @ParameterizedTest + @MethodSource("testFiles") + void testHtmlParse(File file) throws IOException { + Document doc = Jsoup.parse(file, "UTF-8", "https://example.com/"); assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); } - @Test - public void parseTimeout1596() throws IOException { - // https://github.com/jhy/jsoup/issues/1596 - // Timesink when the stack was thousands of items deep, and non-matching close tags sent - File in = ParseTest.getFile("/fuzztests/1596.html.gz"); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void parseTimeout1605() throws IOException { - // timesink with 600K of accumulating attribute name - File in = ParseTest.getFile("/fuzztests/1605.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void parseTimeout1606() throws IOException { - // https://github.com/jhy/jsoup/issues/1606 - // Timesink when closing missing empty tag (in XML comment processed as HTML) when thousands deep - File in = ParseTest.getFile("/fuzztests/1606.html.gz"); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void overflow1607() throws IOException { - // https://github.com/jhy/jsoup/issues/1607 - File in = ParseTest.getFile("/fuzztests/1607.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void oob() throws IOException { - // https://github.com/jhy/jsoup/issues/1611 - File in = ParseTest.getFile("/fuzztests/1611.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void unconsume() throws IOException { - // https://github.com/jhy/jsoup/issues/1612 - // I wasn't able to repro this with different ways of loading strings - think somehow the fuzzers input - // buffer is different and the bufferUp() happened at a different point. Regardless, did find an unsafe use - // of unconsume() after a buffer up in bogus comment, so cleaned that up. - File in = ParseTest.getFile("/fuzztests/1612.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); - assertNotNull(doc); - - Document docXml = Jsoup.parse(new FileInputStream(in), "UTF-8", "https://example.com", Parser.xmlParser()); - assertNotNull(docXml); - } - - @Test - public void test36916() throws IOException { - // https://github.com/jhy/jsoup/issues/1613 - File in = ParseTest.getFile("/fuzztests/1613.html.gz"); - - Document doc = Jsoup.parse(in, "UTF-8"); + @ParameterizedTest + @MethodSource("testFiles") + void testXmlParse(File file) throws IOException { + Document doc = Jsoup.parse(file, "UTF-8", "https://example.com/", Parser.xmlParser()); assertNotNull(doc); } } diff --git a/src/test/java/org/jsoup/parser/ParserIT.java b/src/test/java/org/jsoup/parser/ParserIT.java index e5dee55b1d..54d757e77b 100644 --- a/src/test/java/org/jsoup/parser/ParserIT.java +++ b/src/test/java/org/jsoup/parser/ParserIT.java @@ -1,6 +1,7 @@ package org.jsoup.parser; import org.jsoup.nodes.Document; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -12,6 +13,7 @@ public class ParserIT { @Test + @Disabled // disabled by default now, as there more specific unconsume tests public void testIssue1251() { // https://github.com/jhy/jsoup/issues/1251 StringBuilder str = new StringBuilder("<a href=\"\"ca"); diff --git a/src/test/resources/fuzztests/36192.html.gz b/src/test/resources/fuzztests/36192.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..60cbbed1773aeb97a450d055c283e7e795e08f67 GIT binary patch literal 7089 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 literal 0 HcmV?d00001 From acde180f094e2749d22034916cb35914289e521a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 Aug 2021 14:20:59 +1000 Subject: [PATCH 606/774] Compress harder --- src/test/resources/fuzztests/1538.html.gz | Bin 186 -> 186 bytes src/test/resources/fuzztests/1539.html.gz | Bin 73 -> 73 bytes src/test/resources/fuzztests/1542.html.gz | Bin 305 -> 280 bytes src/test/resources/fuzztests/1543.html.gz | Bin 203 -> 203 bytes src/test/resources/fuzztests/1544.html.gz | Bin 366 -> 355 bytes src/test/resources/fuzztests/1569.html.gz | Bin 2006 -> 2004 bytes src/test/resources/fuzztests/1577.html.gz | Bin 771 -> 761 bytes .../resources/fuzztests/1580-attrname.html.gz | Bin 1184 -> 1175 bytes src/test/resources/fuzztests/1580.html.gz | Bin 817 -> 813 bytes src/test/resources/fuzztests/1593.html.gz | Bin 1126 -> 734 bytes src/test/resources/fuzztests/1595.html.gz | Bin 668 -> 668 bytes src/test/resources/fuzztests/1596.html.gz | Bin 3712 -> 2796 bytes src/test/resources/fuzztests/1605.html.gz | Bin 4977 -> 4332 bytes src/test/resources/fuzztests/1606.html.gz | Bin 1446 -> 1406 bytes src/test/resources/fuzztests/1607.html.gz | Bin 1457 -> 1431 bytes src/test/resources/fuzztests/1611.html.gz | Bin 817 -> 808 bytes src/test/resources/fuzztests/1612.html.gz | Bin 2376 -> 2223 bytes src/test/resources/fuzztests/1613.html.gz | Bin 7088 -> 4801 bytes src/test/resources/fuzztests/36192.html.gz | Bin 7089 -> 4802 bytes 19 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/test/resources/fuzztests/1538.html.gz b/src/test/resources/fuzztests/1538.html.gz index 77129a2a65bd99d1fcbd0bf6e9122b33ea2c6ab7..8131aece486e06285ddede2574705730845c81e1 100644 GIT binary patch delta 17 ZcmdnRxQmffzMF&N(5?3gOcOab0suAV266xZ delta 17 YcmdnRxQmffzMF$%oBGoPhKZaT0We+#rvLx| diff --git a/src/test/resources/fuzztests/1539.html.gz b/src/test/resources/fuzztests/1539.html.gz index b3c7f9f72b264a033fbcf05e13190cbfa82ae4a7..248e771bb4db567420bb97319447edd045d74555 100644 GIT binary patch delta 15 WcmebD<dpB`;5c;aeFD=&P6q%cTLm2e delta 15 WcmebD<dpB`;5e-PG=X6vrvm^XRs>@J diff --git a/src/test/resources/fuzztests/1542.html.gz b/src/test/resources/fuzztests/1542.html.gz index ff30bd3dcbf3e2a5d98603f023acd796ba4537eb..45e8feb45129af6dd6065fc9eda2d9c64224dc5c 100644 GIT binary patch literal 280 zcmb2|=HNJV>wN+fx1p(tkzPhgZVtoSTc<e>IS4QWSZ`qSx4d;=g3zUAfr+fer_8q9 z2;%B|5ZWWhv-<;^Q{KtsvbMh}6NC4~|Nil-tm~8Br_>J1WzlwWkG{LlyS%uj=UwH} zjXA79;JUZ#y0mIr69g<`Y&Bd!Jjn1=RNbvH?QMXgZrFrl3z0N#FfQHpFlX^Mu1gLg zAdnXP+E6s)YL(CW064f&a%0;nh5a60b|8m9LF*KY+X0UCKULN@YJk9*W!I9mGSZK7 N?Y`%K|4|bI0{}XpfinOA literal 305 zcmb2|=HRF{dz!$&ZD?v@q?b{Wo5S$-)<Mog4gw4g)*IO3?Q$P%V5t@m;e2sA+b8Fw zlSs>+Cbxshch5MTcz$DUYjaMY_SV(M|L*Fk(>;}{@v1EN+|!a3pKtz%sXY~1E_>$I z@lPL1c3;od00JlNd7{n}K`byxy1H}$@!*G>AlKyZ!fJWL1!Wh~yE?b|IwF9e7fAA9 z&SKtY>Rp}N{XsGhvuC*`%4Iia8uqKco6-aWN=uYP)Yc!$&zA1do+N?<ghN^mJqfkA jozYzAItiruk-q3cgG$k78_j0S*Lb)4p8x$vO$-bGpx23p diff --git a/src/test/resources/fuzztests/1543.html.gz b/src/test/resources/fuzztests/1543.html.gz index ba0c12344d61c8fce7c895b2f6b7fa989807c046..e0376552fc22a49ed2a93579564968c0c010a707 100644 GIT binary patch delta 17 ZcmX@jc$$$@zMF&N(5?3gOcOZ|0RT7{2BiQ1 delta 17 YcmX@jc$$$@zMF$1&Ei=C!$i(Q05FUNoB#j- diff --git a/src/test/resources/fuzztests/1544.html.gz b/src/test/resources/fuzztests/1544.html.gz index 296f258d4f4ecd840e9a26c475d806c85818f690..cb64ef26d1dded1e7752a15fcedb46ac914ab29b 100644 GIT binary patch literal 355 zcmb2|=HNJV>wN+fx1p(tiC#uYZVtoS3kP`}3>X*=%>1)@cLhhSiOY|fw^-(hO}OxF z((DILT-E#ztRf7=gMdwMpo)Jq2Qo4+OnEy6rups3!&7u%99(q3W^X13X&GG7G!XBv lyfrPkR|~tz4f>^ANHPl&4Xu#4P-ewhUdf`CDPjC93;^b8E1m!V literal 366 zcmb2|=HN)Pc$UDxZD?v@qL)#Uo5S$-+(BNK00!5Kv(nDLKkBqWKq<{U)StCV!RDb# zdVRiWNH*^Q<|q6N#Dj$WhWnwaH(bm817un}nJx#@zs}t3uE2a44;S4a{P!|5LyYoW xc3di`FaB=H`)Mz&r+;C?ZuEz|`9Bzuq$UJ=)gbdBQV%T6B#T<6gz>X5005hkFlYb( diff --git a/src/test/resources/fuzztests/1569.html.gz b/src/test/resources/fuzztests/1569.html.gz index e97c31f3596b3de09fecb10f6341843980ccb403..9901468d818846e55a82f20116358701905412f7 100644 GIT binary patch delta 364 zcmV-y0h9jL57ZBTABzYG!rJd(0t+!UHaRY6bZu+^?bf?W!(bEu;CA;RL@<NPuNiA4 z2r9;*9TXfKBtoTwF1k25_&kCR?z%hFcM<2Bwo%&JRO$s3z6>P^=klLZ_(&8*``OWE zR!=TYPm&Pd4P9A?LlQzOWT6_C!&^uey7}u?y<JZhRu+1Hw|PMRvsM<>f3Xs>yk4)_ zd9B`lOr|&K+>WKk{q*_vS?hRhr0b-!^`eT5=sly6Hb<g&x6`e?!<}@upBBGWtcJ_{ zHY=I=cc%T2KKryI;p3woOc-^sRty^F(T%~>p?Wi1d=U7)u8nEyiW|+eQ5`Bde)vhH z2l;cAN~NDMGRxod*{QSM=6|Go@i4vHH#>#mVRe?pT|IYS%KP14KRm_jL-9*RkIwB6 zudQ@t5wkG?wgL{=!j`{l3&99RFoO9yVzW^Jc>)T2-4>eCl%_PLDYG#FQUMJA#FkeB Kx~V^C*8~9R)3Xl% delta 372 zcmV-)0gL|B57rNVABzYGs><hJ01GiSHaRY6bZu+^?bN+W!%!3e;CA;Rq+kY@s~I&V z2rAa09TXfKBtoTwF1k25_&kCR?z%hFcM)ez+bC^qE5(Y6Uxtu`^K;J$d{GqbWrrJC zlAfO&ry;)WyRs06G=yfzLOCpjw~#En<e!^KD@iNMmCj>-9+2Ov<D$ABmP3};>ohxC zNm>u-<R<N#q4c<$KHoZR9#sdrjyhW>s>q1mGwQX*K=jU5ZFBcvySCF!i{C1i!$tm> zjhXp-ru~pU{j>w&!=vtx7<FN#7&OkK>%FN%`MSS&FYtX`>yy?M*BiBZxv%8#;YXG3 z<<FH$rEbbFG{5JwQ)ivc&!l|uGP&CqJB8w9b(+OpKD|7T?{{}~{}`|J#V-~;+BY*^ z+t`&wv(W(-0+XNtqYQqpEd(PN!3g$u1!J?30agN&paCNc{JbsyZ(^F#l%_OgvylN& S0S)Dk+VTnmx~V^C*8~8BRkl?C diff --git a/src/test/resources/fuzztests/1577.html.gz b/src/test/resources/fuzztests/1577.html.gz index 953067b1b6070656eacdaf9663d94152eaf5be06..e7e3a95ecbd9d6aab4c9d9d281b18ce26d291e93 100644 GIT binary patch literal 761 zcmb2|=HNJV>wN+fx1p)Ixn4#|ZVtoSE53P`9Yh!&lrRS^-SoOibfVJ1rdGkEz=IDq zu{>p1bN5=FlIk{5Yi<ixbB6+9z6;7<B-VKUlX|5k$hdIIwD*=}X>o66{=65#Sz2VG z@oBM%w%f~(JLZ*Lw$Ryk=<?|oR}VYN+`G1Lx|({ocSoPw>@WJ;gxf#fi<}Xo_B?OL z>@`|C{fD*R9uq8G^<iaI#hfXn`%lc?Z+f~QviIoP&;R^H+CQ$CxAl~|{PdG<kN)j; z)#AI#*1T3GIdsv-C;IRAZFE_2P5$&TU%j&K%V#&d4-a{G_DRe>x!wO4&YfB5`)GA< z8co5yRXb0dcKmeE&KLpiYke-3K6{2R__Mq74zfm|5&s*1*2pcMTq3e{*+;9Cw;KN{ zZ|)Y`9O&nMbVtv3=PUR53%?poncIBs;OgV9b{ea`SZKcfs4-W~ihb>B!L4OcYpX9y zI+bl)TdDT(uj7f$NmG_Y?w0!Y`BSv*%#yhNA4R-(^R~;nK0g}~IxT(m_1)rq-3M#- zcDq=+gwL~ymR$Wa)JW$#@66P~>Tg%hG2NDa*M4&OiOqHQ)-T%|xi#(n<+SaBvn8(! z<cA9G&xty4KJmi(S9Nt#=3GDW_c3(OYPaFsEB<lL#MmF(afSK6>Syj|vu4r({Cqci Qy?PzP$2omRt@Rig05MUASpWb4 literal 771 zcmb2|=HU2V`6_{d+tAe9TrZ;}H;3Wv72mwe4k8Q>N|=L|ZhGA$I#KChQ>$Q7;K2u* zSe`PhxqK~8Np+j3HMa$;xkG_4-v#9_5^KEwNxjk%oY)}t>b>Q*^td-Of6miwUsY~$ zX7gf|v&T<<Tr#V4vW1TS!<VUFR$nyW%U^xLze@eIb4TB?+h6qa)c79n-E3g%^Zjmt zd1PutZhQExt-7zKJPiK%VMlmKv(4)N;rfSDO{Y!Q`(+&{qxLxKp4PmY7}e~~-|wfb zw3rgjv#qSBwDZ>7{d<006Oz`lzaQ3nJLV0qpZW29@xiU;mutUPy)nCR?#v#=k6Sx4 z(r5{uMVma2G<;K=F>Pn(`I&I=vv1~nbHTG`2!lV>oogDOsoA9?yYiWRu3h|*A1adW zYa{pYWm<RD%Y9pSv}VfXLOabze+78IImhhdaecm^_`|EhdAy&EEZ>P<O__5>Xm0bh zs~u6+Yp?CS$)mV;$I8lOAOAX@xKwk;`%;;uRLarqSynF>$$fk(wd-!4b=SGq8`tTa zzqY<yU*`GYqgB&8%R1KGsoZ9E?epn-D{h#i%sTe=XRNh%PW=}7W&3Ts*>5hG^Yz)) zS@YA+=IL%T&Qtgo?)qm_E~9<>!v$;DZf3@4KiL11Nhe!wQ{0RF9pXJ-7Int!A^Qr6 f{cL|~-4z!TV>*E1%V(|&+cSKe(|6QbkC6cYnO|vB diff --git a/src/test/resources/fuzztests/1580-attrname.html.gz b/src/test/resources/fuzztests/1580-attrname.html.gz index 952d86bd91e9ddd8ab910a580aec0f037abd3eff..424d4ab09c4feb53ff3c580515b7ef7a2b573e43 100644 GIT binary patch literal 1175 zcmb2|=HNJV>wN+fx1p(pfo@_+Nl{*6ZmM2JNp23q+Y1Z14h4ubB!=t1k*#S-H0XSB zOjNIrx8&%%rlUz8Q)_~o#hP_A*(C3a^F2S{Sugl)<Ex<CEs-6*(|3t{uN1tt`<3PE zD@SVNPB}+zziK9;xNY{y7kAYd4BTe2Fu;JpmPs(?NTU;`yb~Hhe*Uxi=*uE2`QNWU pZT0z`fBk#$2UAh$5#;}2>(z$&CsZi+T!X(2gG$JEF9rqy1_09XOLzbP literal 1184 zcmb2|=HR$-;(Y=Gx1p(pfo@_+Nl{*6ZmM2JNp23q+w+E8ha3bP0&~TS+GP|YB05(j zwkCKSShDf@p<~C6ovsMx&ed_rVTqjjy(>{{+mGNDy=k>q%@ReDE{E5O?q0+3xwLY2 z>D41SGGen`?@n7C&~ihMz5e|iD+UI;q!~aG2KHUlDTT2|94%kyDLaBZeL#Ei-@^JE w@BA+Rp7j1+to(fc{hzKZXBt5vFl@d0LA`AyGv&T}usw!RCFHvo1A_nq0F;4Dh5!Hn diff --git a/src/test/resources/fuzztests/1580.html.gz b/src/test/resources/fuzztests/1580.html.gz index 15186dc13cca4481151b2d2f7b81f756dff5ea64..bd3551074b885b4347fe43f868eeceb155e51be3 100644 GIT binary patch literal 813 zcmb2|=HNJV>wN+fx1p(pfnG*QZVtoS3k!K03<MkkBN$KfZEy%asKzm=YxVAro?Ole z9`}mx|C77vJ<mNRd)ub%Z-f~JC78cUcTl=()KNnr1RCylM17l<tn<0T_3ph_f$|YA LHf`S+&&>b;mB1<5 literal 817 zcmb2|=HOVG@+yIW+tAd)Krf>tH;3WvxrMxk9RwH-E@=(;E@1wl>VY4RlI@P<@O?|S zdmC&JF*-ag*DZB*ZTJ14#cLOTVr3YN;K1U(dj7%auu(S+jS$%Gn*Vx*rTzQL&VPHg P7SE4(v1$9pcy0y&%rYy^ diff --git a/src/test/resources/fuzztests/1593.html.gz b/src/test/resources/fuzztests/1593.html.gz index 8b8eab4b50937d1bb16011ddab47acfb9766275d..a3b85e876745283e286144f4dec3cbbfd460c517 100644 GIT binary patch literal 734 zcmb2|=HNIM_AP;l+tAd~STCa_H;3WvT|+-<M~R~!85Ngqba~p=*HrYBIb{pSy9sKt zWl9@`ZcN*&SMXekU4h-fj`8>dC%Xe_9GMdsR8yYDUfcgyCvsEj>z}pf@7Jd}6zMGc zShQ>AdAHYBug|~!_g~uHUxjz=d%LT$1z*>>f8BMR<(ldTZuZ-{7ZN5eI<d`+^9jF0 zk$?i@B!_<j9IjD3u0brWe;SqeoCHM#PslSZ^ypsb(Y2t%KH!L>Mw1fL_Vsb^c`BXb zJSYBTYYf=(m~Yn2yCM}SX~G$4*&nxU<Dd0##Ze}gil5gj=6>98?yv!$kVnE~rp+fU z;=et5V0Pr-rs=O1czqAu^)_5TE>Zu_zOKxf$3H(@q;uJN&Jy-V9~0;FZp!)h!^e;> z^2Wn=HS3a3hR5-&-}gKz?*HlS-xlV5;=i|X|L-1u&)&l)w@XK}KlhROlAe{$!}2R$ z#d+UnzNo}#Ae7$MwqA7GBqi;fy~a;?E4MDzth#^NY4Y?q;f|;kOP8$BX8OtOnjbdj zvp~;*4xqm?;#pk1x)yja`_^hQa&K;P_;+J_$<>4Y0W7X8PJ)dAOO6JpxUzg|cL0Wf zYAIXy!9`^@t~u3V{7J3~4q1i{UK5=a{y7M;FXZUrc)|}=A8<ro<B@`hqhRA7MFF7U zo`Kw~n~qJmp5z<{RaLY2-`c)2IZS7Bbhp(`Sk$jEq5l0UW4UXVCoMb_8hyFWs6DQ^ z+1-3wfc<pbs%EXb;V-wv@2zOvyZrtIt!1fl=i9qC`MfWfB-e4gz0mB`rk{_Ek6NTS zmj7G*?+?eO8Q1@RT3f4Oe?9ksb#lh%b>*Am?v^G!uh!r1m&jSdwfd-EVr@RJ7@w35 zo9khwn`uvdAzlGG7aIE<K+jJUbDjuF2T-2`xG0EZ8j5%ZzDDs%KKCEiNA=m0ZP*zZ E00d4>K>z>% literal 1126 zcmb2|=HNIM_AP;d+tAd~STCa_H;3WvT}Qv*M3J_Ks|^dC9(8mYv++oaJ(w?b^YBay zVUvxWPRywc#%y{H;t3Irrwg2Q7Q`Lx<a`n{`}FsD#nrziO}Mb-esPE5xm#kVzZF(i z*Ok>2|NVCR`Q5v>XTR2;Umm}`yKr|#^8Vzn0Tu~P4vRJA3^EQW{<^Z~VZ+7-!_bJ^ zw($<C7c#ood1e)K*qP>AENEEyoO=a}l<&s|M`6B;%b)SGix_;ZVVsJhmucBN{RKi9 zCP$gsd@Wq&&oY+@IB?}NYY0>CvWf!>INM(Ye{N%5#`|QNatm`CGqNDpjH-PNUJEXY z@JDbSVoYq%U(D=$PCS6qWY#0bW;K~Zft#YY3tPMstIgU{;83K%cY?D<c)kaN`QP6v z%z1SY9|{~8gPGHptAAcSx9r2#Z1!;L+Vj``elPw1wWRj_;_6F(D|M!=uC%t<6|weL z`HlDW7rw=9+5GtCn?K);yC>&eYqkIPv0=Si#p`WV<zHSN{u4O2F=$^**_Ed;B^Q%T z-yGXrzw+&iFMm~e<(@8K;zJ1rAL$di?i}nq?4YnQl-QB7A>>2R&A_krZz>)(a55r$ z606UT95@?yZDRk5{ii-2W)x<^62iA1JbfGEg+C_#urz#62HNFt^z@<oTTcqqX59k? z$$4lH9)DX|^1#-4)zX)(%!r_tFnNfs5E#~r{O`^yIMCAI7HpGnp~d;j%F4rx$&AUX zHq>qU{Npd{6%;QKHtei4pGLx~pG;bf$Of|dpr^2eTMl`aP8JCkIFj8|1sMUhA4XON zPta4{69<_I!avmPCNkvxn(o7P=g)=*4;z@4v7Phte->{2_CfY_zIA24=B@qx{`J4t zFMr*0|Ni8i@fNYuSNF)u#jXkaYyIZCeMYtZt(zZn%KrVHdDPSV^&xqA=^x*nCmgm; zW|$g%cy<5lK5@Po0vF6G4bT2x4$MREw>;cm^`cXL#~I<a%-twiBhP;jWhto#`j6_f KC)=<yG5`Q=N)LGe diff --git a/src/test/resources/fuzztests/1595.html.gz b/src/test/resources/fuzztests/1595.html.gz index db70068637689b901fa4c5587365282980734de8..b1e83230fc495840506d939f7e043d72858137ef 100644 GIT binary patch delta 18 ZcmbQkI){~0zMF%?&+JzM(?-t8OaL%E1vLNw delta 18 ZcmbQkI){~0zMF%?&+JzM!$!`@OaL%41v3Bu diff --git a/src/test/resources/fuzztests/1596.html.gz b/src/test/resources/fuzztests/1596.html.gz index e32443802048dc989bab5f735a66defd125ecbb9..1d2514f668af9ac3cc0a682a43fa628715b91d4d 100644 GIT binary patch literal 2796 zcmb2|=HOs*{guGPZD?v~rk7EYo5S$-s&Af9qRfHBa}NYF9-Ufp=7ZsueOWfn{tmgP zO<uBh>OZ>VCY~3&yYL~WO^fvQcXujTzh9M%;^1A7V5s`z%?yJ-|K9I0{(ND@lar4> zEUC7v2(j&|RBw?;k~&nz^YG=~PY)JY*>qJ;onL8V%UO9(v;WZFwbM@?4y$zQxcWKI z#?J2b*JK&Cti$t54lA_m`C%r@b}H(gR+jtLMWLMQFR%ZYbt`4PO-Aqe`E%^vi-&7V z%ne)FbF6)bb+zJA&+Fk9b3*G*xwlp(Z@M@mUp~J_!~SobWbo(X={?_e+HXyKTs*0> z^xxZgcOv%0#vIl2)2%!A*sl1*wSzx5|9kl`GUh>4`nu=SK8x@9Sbyi^+set;D(B|k zU;n3X&B;`~k4#^#ePw+StDgV)Tiae%m+s?*sta_#`|bPD&VR63Rjz5tJstb^_V@q( zdv*5c(}ZJtO0Kom=v7%~My*fJTzfCeTsrdF9nGkXo9l}#0>ZF^fBR3Jo%R>2?7g0% zv+LF^nsevwEa~#y()M;SHBpau&rs`Kb8%hz+mfG*@%M6{9Of0@ae!5?ppiS~0n;dR zG#o|~!Dwcne@T!dq9^CYzqH=wf5pF#^tXPr1RO2?N2|fnHo)|+N1uutN9G>Cd7F&^ E0RNvqM*si- literal 3712 zcmb2|=HOs*{guGLZD?v~rk7EYo5S$-s%_ugbjG&C^^K|)LZU%7kLMR2Vmu<XiK$&h z&im)aBZkXVnwqA~YHJeRz#$c7Wwp&g-pII9*L~YTzn&udN3Wh`oV)YBeBX2H>vwjQ z)qT2r`24&2k3ZC}+gr`MZ!Oc;{`lp?58pl({P@YQ7hhBG_tn*pd$a%B+1Gvg`0&r4 zdrwy%=D+{z>&gE2G5a1D?D==|@8f3n`F1rWHgT+ZF@I0~J^c9a!#|I<FAr}#ee37k z?pyBWdQsQ;_S^pb{c-i>d2eI)?fCrV@A-NEHtqlMifi7Ux3{*cAD8cYzwd{W-n}nh z)%RGv{<QqFdh2xA;%`r8)c^S@?H=>*%Y6OEJ3r6g_xNOO-IqJO)xYLVPG9%=_PxpZ zF?)9ZP<!{NSp5H_<9}P*_k2B=zQ5wj$@ckj@eeoe`%ty-@z3=Zf9m%>&fouL$*0nP z|N8T5CZ0H6)2n~r@GJ2L53iqm|LLvmp6^_XWaVY<b7ZXlKKIYn{~r{8T+Fs%Q8&+z z|M%yg{{6qt^XtC<6z`8Oxc9Q#?nJzP)%!W~?(L~8t9txw?(XgRbLZZzd%mc2&-Z`F z<?X_7fPeqB?(f@NarG2daDICJll29QYCipq{k-=5_q;!^{pbHz-Sg<>$+erK%k$^m zwXOZj{O?=)Quhy2PZvmPA6DI^6!TzHT$^`^>y87ddwHh6=qhMDT_ve^QS>3x^sh!S zi?k21>b*|g;S%1$9dqq;LFYOS@f~ZXKNN};)GLV8A2nb!3`WzzXkHjC4~BV}m>>K- zph}~df9d~<e~&}o2dx;bDn_fz(I&%avuU*HINE&1=t5NTP1k2Mj?6uN^EMj;00mF^ AMgRZ+ diff --git a/src/test/resources/fuzztests/1605.html.gz b/src/test/resources/fuzztests/1605.html.gz index a216775155c59a18c3d9151129c1cf2b8f5ddfc6..3568af3f33cfa4ace868411c2692b204936362f6 100644 GIT binary patch literal 4332 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 D(<4bc literal 4977 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 VZ>*L_p3Pc7+4E7er_s!ye*!slx03(> diff --git a/src/test/resources/fuzztests/1606.html.gz b/src/test/resources/fuzztests/1606.html.gz index e0fd39d1e272729e387d490bad9d53776bc809f2..33f29d5a910ecd785389878c196a9223c3e8cf24 100644 GIT binary patch literal 1406 zcmb2|=HNJ|&6UW+ZD?j-rk7EYo5S$-nxo$#2N8yVz6XLlordc!=>9xb^197fA+x|x zDmne(RvQNI^GlrrG@F_PFU+{U#q+oI+dtbr|1;5J3q1PZ)h4;8E7%I>=3jao-06}n z9cTLYh5PE%-*1XkPTlfRTYKQ)Vc%EFAHMyur~2Al)AdDP+I;6ObKjfz<x}kwE5D*C zbD!-H)jOX5&_8N_+v#_AW2ZchS>B%;et%((`p(k%rM7RB?nge5{<-g1@S5GvcC<W? zHNE(<gm2l~Y1Vg^JWiYP+Dg~%*mKD>=ifOOz3<fByZGA97fV;TmrnjZH)m(nSKqQP zLS}M&+HxiPn}6^xJ!Jp?&*$?)g80Ax!~3HeJzR?qofs7EeSgJXu|*)M?aBQ5e?Om- zN_>A(f5!g*{`dD+7-pT3`X@!Y38Om533p0Dt^Qs8YS)C9#mjo_(mL1wnX#OEW>~b) zA9Jr$^;Ke~p1z;_AI)!}oumG*|0Dkqm{*_C&I+2DTpzt8N#JTuwchKRnaAT9PX7O; JJ3p710RUQ(0qg(( literal 1446 zcmb2|=HNJ|&6UW&ZD?j-rk7EYo5S$-nq%H&2NAXhGL6a$yjHx@+R!WKbZ^s1$xA1+ zx;0mQ(Agoz{$f(`!4S<OxiP99nZLdEpP2S$`o`zmZSUXsm?;^nbKd^@YpJ_N`>ve+ z^f-O>k&M{6{M`{pRy_T2|E|}T)E)ceMDNw@*XiE$tLo#&Cx=fis|-)xC-tjnvw8f{ zJ$2o;jz{)xGn~_RD)+zt&&F%ppKhxx70uoM?P=ZZm&RMZuIW3sd42ow6T3D4IN!Tn z)meLd&bCk6Rx74$zBzk#*=||6OCL9^tBrpbckD3dx8=LV-{o(ZTW#`7|C#EqQu`~* z`EK{kvpcDLx23uL@R3%#w+enDt@ZzYKA%rMabB9PCJ6s3Pxk3ia}!qn|L600dn7{h zqH5P6WC1jJVz|$H$uQwR_eXyY#g+=^XD-t<xR6{#jE!JF$e;et`u~sp{{K4tr&d&Y zE<P50I2^-&I83X*rGZ0+*s!R-H);YX)+h)E`_=y!`!QMfZ<hID5MBEzao_&QTdXHN zkO@BZzeM+$O7W!pN!Oe70!1jxYR~QS@tC0gXa8ehK8{~o{~k|np6vFJg3JyoC_dB5 d1pEH~5pLTf>$j%<zJ0Bp`Q-m!y7P0H833196t(~W diff --git a/src/test/resources/fuzztests/1607.html.gz b/src/test/resources/fuzztests/1607.html.gz index 064f7adc2dac387fa604c45ef337fd3fd0ca4e2c..fdc3a52575c605641d99a93a94a84138b149a7f4 100644 GIT binary patch delta 1274 zcmV<W1O@xC3zrKHABzYG8iWgB0t+!VFgKA8AOX9PFg1V6o3Xw2{yNztDkw9_uAiT0 zXWpB6{^q@T<AfZlw&$_!YTcUw5O&$41q&SefhlUtCrI%zgppUS4YFhxQ6muYVrQTf zE)WQ*M;%dMzU|lQw(PDZXrPFJ>JEdDFc2I+0pZLI>^cppn6v1;yTC#lEXKvv>v#hc zconZ9>t%nYH(i1{kqjy4``fMbh74C1Ha9EGSuV3m3tkQI*W(*up}eUEk`b^5r*8Zt zN9ugx1&o(J>cB3COWCIoG|DY9*!_{a`);_VBLx|vDYsa*&$$Uz9Q&l+5VUgr-phA- zhj>3n+k!<v(5A2hYLB4js?#B?L$MOrSAp?o&wmf}C@p|8hsaXi@39+HVw00{0WxTM zAc<zo=jI#C4-U4$I#6Pm%&*wOwzuq>C*N9&T*=ehP8d^<M4Fvv*d*JoP%l>X*3O4F z9m*O)p@1iL@SO%Pvcy_DEf3n?8cQLWhT{6l>dI@S^^G<(pbU*N&O4Wc2R0ODu<s3% z3<4N`gpW<s8=k0fR8uwumic@cz+oQ1MU<d~5`4Nr&VfbR?aS?ODWm*wT|;j7Amr;g zB@arMdq6Z?qn9ZGE2L|b&LzYGT}ViW`66Imn0bg}tgdFaPWKT3N*D2t6y_ISl$JJ? zK0)HjTGd7B{u))6-8(;j;)Q)O$N_1(8_IHjs9ymnvi^FpmBhojF^|)XsOPpz-O?+T z#P^8qbh6r0o48oAsv~zn%Dv`ZKq2H)Sx*iK5?JR0QT^tJIkmpwv!}3<>n_I4sdeP< zO#5!m4~|=~JLZ?E%L2;nP|!|(f$Al4v(p_0w7SaP-rIW>oFM%F`bOV$jhUxYu$8=j zw`c_R9m4GOf_fGf)V!VtwVlA98{5g4t}h;SphrrQE9htm^E*&xtZFF_!FL#MQT*YS z9gt}*9Kw{_o;VmBn1@SX^D-e>Z(?$HdBpG@$HzXpjUW3pj`|!gOhCQct+$RQ%w#b3 zFyX7YTH4!P#dU9WrKB#UmDOLr`a)7T|8$T>Msqc8Xo5adE&Tpz%W5`%x_C7>oEoa` zvhc@Sd!IAE9&%RmG48IgZGS3WkoSiG**p6)liLF#fA_{`mSKhFr4u&E-lk3sv33ST z&KaqRW-$&+BOg8Mi)Z}ss3>oJc6K%i@yPhtuP`FEnk42bP8xIhy)h=G^I2C`g`C-l zQ?xiOyaWZ_D0?;Mob<Gd2-Pt=<tGklTD`0NsZQVFHt3?j=R~*Zx*m8E4E<&NfunP3 z0nxBhe|Z7>?)|!tK_$*O4QtmZT5uZQJkuZGq|mXH8H*G;YEjtq?#F4^)PdUXoK30C zk<6P@Wm_~rp6_^wF8KGf@m#cnqV`Rmlp86_a-T&p&?L+vQnI4_N9~l3UmD_2JE?k~ z;GyjkXxP`VuVG)qzJ~oX1^Zc4>(6kUgqgSce;`vOC%{(e3@CF58%}u`!ZsgEn5`kO z3&Ns>1e$^MWtoO%l$43PRo=}5xh#mOgp)}TrTl@`bq?#y<gol9`kwGg+A{?(E|vP9 zbzXFaLuWYjkpT_2`pAGjGN6wP=pzGqLDfeF^pOF5WI!Jo&_@RJkpX>VK<AEh?nvj3 kAaw3X=Z^G|0S)^a_BHHl*w?WC|2;DBFL^)EH#t23061fN`Tzg` delta 1320 zcmV+@1=sqQ3$Y6iABzYG8iWgB01GoVI5jbm4j=)#kuNoW;mz1ydw-p55?Yj*WY^En zvor6_Jb&}vym3MfRonB}R<-U;0SLS7(Sikzecu!{<`bm&2*SuKSNmDAi>MI@dGVmH z6fO`5sYe}AV7~3w>bC5zCTO6DzUmHxkT4J&KLO#)4eUA%shF|o-8;ZS8!X1fm1}qd z6nGV{A?s#;rZ*jeI*|-1=lff&^o9&p7dAF3%vmh6N(){M@YkaoVWGUK29go52B&WP zBuDCe;RTGAKkC3PhYQ)K5H!jy(%=1&yZdgirb7i8peeUlw$GU{RUG-G-Vn5M?e5FB zyN7r$N85r$K+vYJ18NVUXR4DStU<97*jIq@XU~5R^e8QWGKa`g-tV#-RAPgZaRD-a zx-W@l%;)AC%n$arz}i=0n9Q%(!nQZ<nkU~{i(Jmr+fEo0k3^cCXV@g$s!%Uh_2%}6 zHyp|uLZN^sw(*??FS5j1J1zIy-x>=cnS|on^2+jSrM2}oG@uNPGR`}fga<Yhrm*jI z%K)n?Uh%AiZ8dP_$|d{-Baonpsw(+^Fo2Iu)El0taa2<_1(x}I8Ngv4z(tgxgc5wZ zPR@Zv+U=!wxR6nPu&yDuyAbmAoRSBni(McZuF@q+zzXRqr85aJN9PjKVZI2M7iJ#f z7^|z<sndBxfYN!qBZb-d7p0|5q)(8zyjpdUy0=QzW#{(KpLk)9401r4?z*yn9O_p9 zimbO@Y$fq<uFv8$BkH;BQm1r_CGkC?4-T^0Q=7P0vZ^C@PRhONUO*w_Q&~?A2ohN5 z15y3vhZ(iL;j^c(lIzaL&8c<d?@aq{&JT`Tusi0LsmlV&?NHGEMX;|x^%A*p&>04_ zy2|eE+q)H<ApHOOde3x?n5R>Ju$8>GXbAQl!tC{edKMPcyq^2D9mAg+*~y5mFCKNE zhf0zw=x_=1J5Xk<YAFxFcNlL`{Nbh@kV!5a!j#*dI2at5hf82{iIA)}F}}MzWO$F` zV;|kdkNp})eU26;px&+4TSpUSJQ#bB@YP%??e47Ly0@}iQkT;5%CBELeIcoT4v<Dh zb0uzQj6PE>{N8EHYBqbic-23g8mjKF@W-3GpEJK6a#r&(?yj&ce<EIx_XYsjJ9|@; z*aISe=lW-sVTI<U6E?~2rcMp9w);fR8L6>mF%C;3A3f`fXZ-M}C~tjwdO8X5$mrOw zFeJ7bC*~@S8*}+aSBy#NY}S=kA!jz?6fI5*FF}Df%3jSmCq3;VLUqhe`H4fCR_|zk zs?&G44Z0}sIniypjt8CuLw^~6;OLxMKs2a-R9?WoyT9&XP>C~6!`fAf7M#X6&-4a3 zDReAk#v+A|S`;?D`*9jJ9YF1O&L-66NaoF{vP~Kw&v!gT7yNtLcrMyOQTrxO%8iso zxzC~)XcA@-DOpkeqjpNiFAZ_1om9O~@WA#7H0*2G*RZdOhJ6kDX9f1NsMed|IJS*{ zV`=q3rb<qLt<o7#<`6cV@*sq5K9(?B17H_~MGFZu1MA5$4a_Jh6L+h;lLvBH5K{>! zlOjs_eXU1&=^WM>$YJ>d^gZE~v}+1rR4VnMbzXFaLuWWfX{(P6Xt>o!2L4Arp*}L8 zj|}Jq_2FGm&p;k-K&DL}8PG=tM#GplJ~E(>4E*;{rH>5gBLg~jq;p5H2d;BRI(KwN e_6aoXYuMMYuZo6!4f|&W_WuQWKhQThJpceN9ES7& diff --git a/src/test/resources/fuzztests/1611.html.gz b/src/test/resources/fuzztests/1611.html.gz index 90215d0a84846de1b61ba9473cd61debd96f4a75..2a00b1a22f0f94b6dcf51a5e687bb01e354b0df5 100644 GIT binary patch literal 808 zcmV+@1K0c?iwFq%L>FNK3o$k^F)nCyZEOJT7d>y&K-3jwV~F?#MFCexK^6iPq`sVF zh!99jz)sHbxsGbbsr{m~9Z<R!7S>KoFu^aV+DHL}!~#r=s1rLALMYt%;?{OzH~kt) z^Ui+n-SfNm?%v(K^Qk{Kw<|lHcWQCDqL-gm1ih<QR4{JCV<^#pYW8Jjkw?^Ja|A5@ zh6FSs9uvX3CB;_abNGaUhh)GA-MP4Kq!kIy5r{q+7t!BS1)SRgu4df>3pCUXhk=bQ z#||1q*Pta!3-XE&`#b@2SPfzIDqJcmHC$;jrF0=!36Qy9YR%Q}N-1<p23VQa4CSzZ zb#z!t=7Z*eW_SrN!&qqW*X&CuaA5gr<&kPq!OU~TvDaubW3kSeR=_OJuQAhWH*lX; zry7f>p>KS9J8iJ6=w?lbHr9mnk1!!;2B-FCVmG%SYoC{fBwwS<U|;8!^OFR6jYXeX zEGb=zxDV^S^`SQHRGp{L%B!{Ce2VvExyfm-gQ9<WwqiV4^iwdDVeA{RifsQmfAG7d z2o5i@qV!BrdytNQc5#s{Bl#%!?q(>uIi}zI(yp6mhg{tpvrTsm(4PV{2g!YgpIMyS z7zRt7b<49HD-mWfK8~x&>gy3&=X<jY5g%H$88u^8$>x{mTjCUxhZ#|eKej=zqQaRw z&J)qooel*GQ&9ao?x`|KT9c$TNm`Snb*hrq2oS?2(eOi<mjr#<2poz`-U0?)As}h| zazL6#54jEmXY3~EAYK8I=L3YM%^5ln=ri1T-K7g@@<QEU^Nh`_j9pW7Kq|Ud0@~x3 z8WG>n5;viLDg#sj--z#c{1FiZPH{wR0BcD)dH-_P$$ikpVxxy%XMg$!2<#xD*zH8Y z9ZAHUG+QCNT`tK!bbfINd(WJ1N8Df2Dv_Zphx2@bgE|&+0zKQ^IpZDsTlqS!1DqkT z-8fA1h?dyc*(UrfGu4CHNfJYMSW4<>H95G%4`7}DBtGl6&feOp{co$@aL}{7S}QoF m1?x3rb}a-*eLUduEfhwYE%3VU0!@_BrvCwb_A{j86aWC7%7C^2 literal 817 zcmV-11J3*(iwFq%L>FNI3o$k^F)nCyZEOJT7qM>BK-3jwV~F^Ipg=05APa#CQeRFo zL<l4%U?=DJTt~Iz)P7Og4k%p<3u`APnBWVlHc|i~u>cbz>cq~35DIs`xV4?wq@jgY z<emNAyXSZB-o3kf=PQ2Q+NtbzUKI-~6|MZRBIr%6QUv`v+=mkNscM~PCb>r)Hc!Ch zZ$LmL;xQ1+Yf@~+J_k=AxJw3%(4CL!hFYQE6oK%QeiprbRlxae;A-YAFhNC4w;5RI zaBQPaG!<I1v>>Z^@SDeHHmf15U4(N5rG~3brj*Y3t3EOZ47IuTSt$kGk^xp`Rb81B zu#PrM$UIP8P<1!f%P<xi{5AR#avWH;T6v_JR4}r-;@EApnZ8u#Ov`7c>(!Xywi}ra zo^&jthMxZE<*d%qqMJ4$>R1ypdfvvHkpBiJ_GfH2w;*kwmxd%C!^~h^=9aUQ1iFnS zkC`kkT@JZ78@-L8Htj^6r_jo?_3wO&_hp&MY2QKNpO&r|O&0wG3}qPWN~9v&zt0~0 zUMYahi>xR;L)7jh<DXs}WGhJC3ckA)h^~&=Z+2<d4YUHTZj9NcyAJ5h02+hjp66#4 z=QfAIEY3OQIgXVOvj`u>)p+&!2(8n-xy6tVtXhm35vyeD!{cpnipj%_6boN=K(M01 znH%;4;puLN0)-i<{v7vInI^4&ji*T~S*A&AnzRlIV%Q`cegF%Sphp|NO_9l4z`zv( zl148Fr1|F|*MMN&YJvvh6(D&&KxkT=p#y=I=bhIbx|k$SikobKv4tXImlO?<3inDt zd;C%(;u~1vCbV~DfGXe%@f{C8B7(pPj)(<dF3V0{Ki%u(HfUn8(TUgDkNyDyJBTQD zJ7I7~5^*O@S4eM{NzxCUZyW;enceM(`)g7qG<0FIo{w-)heD3Pv%Q@&-m$-(t>f6h z84}rz!!(OTc5tQ%FU?H#V0Pj}*KC%M+G<S>F7^Xh=Rb*$`mN(JgMV{v)sC%a6RvvQ vM%Q#}E&q@ftlN;;HRmJsFwJ1f^3CN&n$7XL=KzhB(WZX^efBe?;}ie@h?0w7 diff --git a/src/test/resources/fuzztests/1612.html.gz b/src/test/resources/fuzztests/1612.html.gz index fa1742bab68eef1633abeb4e334885c686d5d4f7..b216f312e3960bf5b2f5871c9128457e39270e3c 100644 GIT binary patch literal 2223 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 literal 2376 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 diff --git a/src/test/resources/fuzztests/1613.html.gz b/src/test/resources/fuzztests/1613.html.gz index ee5eb319f62bd3c16ba2d9c472340e5034e6bcf0..f65c8db22408cd7a61219d585919063e793fc27c 100644 GIT binary patch literal 4801 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 literal 7088 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 diff --git a/src/test/resources/fuzztests/36192.html.gz b/src/test/resources/fuzztests/36192.html.gz index 60cbbed1773aeb97a450d055c283e7e795e08f67..d227815f6d6216308c33ded5378885b70ec3a807 100644 GIT binary patch literal 4802 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 literal 7089 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 From 19c77325c9abb6f8b8b65034470e15faad6ce822 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 Aug 2021 15:39:15 +1000 Subject: [PATCH 607/774] [maven-release-plugin] prepare release jsoup-1.14.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 92ae950704..50261a6af6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.2-SNAPSHOT</version> + <version>1.14.2</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.14.2</tag> </scm> <organization> <name>Jonathan Hedley</name> From 7cc295c186ff363cfd87745d87df2b5bc7fe2f6c Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 22 Aug 2021 21:53:24 +1000 Subject: [PATCH 608/774] Javadoc warning fix --- src/main/java/org/jsoup/Jsoup.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 81b7b724b9..1e0a3d8d97 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -266,7 +266,7 @@ public static String clean(String bodyHtml, String baseUri, Whitelist safelist) <p>Note that as this method does not take a base href URL to resolve attributes with relative URLs against, those URLs will be removed, unless the input HTML contains a {@code <base href> tag}. If you wish to preserve those, use the {@link Jsoup#clean(String html, String baseHref, Safelist)} method instead, and enable - {@link Safelist#preserveRelativeLinks(boolean true)}.</p> + {@link Safelist#preserveRelativeLinks(boolean)}.</p> @param bodyHtml input untrusted HTML (body fragment) @param safelist list of permitted HTML elements From b7318178bf7442374eed8db402fc0415fc297760 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 23:02:48 +0000 Subject: [PATCH 609/774] Bump gson from 2.8.7 to 2.8.8 Bumps [gson](https://github.com/google/gson) from 2.8.7 to 2.8.8. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.8.7...gson-parent-2.8.8) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 50261a6af6..5090d54065 100644 --- a/pom.xml +++ b/pom.xml @@ -316,7 +316,7 @@ <!-- gson, to fetch entities from w3.org --> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>2.8.7</version> + <version>2.8.8</version> <scope>test</scope> </dependency> From c91fc28e74e413059592f8b479146cbe097bef36 Mon Sep 17 00:00:00 2001 From: Steinar Bang <sb@dod.no> Date: Tue, 24 Aug 2021 21:22:51 +0200 Subject: [PATCH 610/774] Remove version from the javax.annotation OSGi package imports to fix #1616 --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 5090d54065..7aa963b07c 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,7 @@ <instructions> <Bundle-DocURL>https://jsoup.org/</Bundle-DocURL> <Export-Package>org.jsoup.*</Export-Package> + <Import-Package>javax.annotation;version=!,javax.annotation.meta;version=!,*</Import-Package> </instructions> </configuration> </plugin> From 38ef76dabb71cef4855db38e2a214725652105c3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 25 Aug 2021 12:34:01 +1000 Subject: [PATCH 611/774] POM and changelog prep for the 1.14.3 phase --- CHANGES | 7 ++++++- pom.xml | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 73707f2fd6..1adcd10122 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,11 @@ jsoup changelog -*** Release 1.14.2 [PENDING] +*** Release 1.14.3 [PENDING] + * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of java.annotation (used as a build-time + dependency for nullability assertions). + <https://github.com/jhy/jsoup/issues/1616> + +*** Release 1.14.2 [2021-Aug-15] * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. <https://github.com/jhy/jsoup/pull/1536> diff --git a/pom.xml b/pom.xml index 7aa963b07c..e2da018485 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.2</version> + <version>1.14.3-SNAPSHOT</version> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>jsoup-1.14.2</tag> + <tag>HEAD/tag> </scm> <organization> <name>Jonathan Hedley</name> From 7a215a7cc349130ad5ae255f73b8d4e69662eb25 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 25 Aug 2021 12:36:05 +1000 Subject: [PATCH 612/774] Tag typo --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e2da018485..e2b9ee9bc9 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD/tag> + <tag>HEAD</tag> </scm> <organization> <name>Jonathan Hedley</name> From eb2f8d27880731cdc358d382c67fb53fd23936e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20G=C3=B3rny?= <70758186+pgorny@users.noreply.github.com> Date: Thu, 26 Aug 2021 09:54:00 +0100 Subject: [PATCH 613/774] [fixes #1616] Make the imports of javax.annotation(.meta) optional --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e2b9ee9bc9..e58b8a1d9b 100644 --- a/pom.xml +++ b/pom.xml @@ -150,7 +150,7 @@ <instructions> <Bundle-DocURL>https://jsoup.org/</Bundle-DocURL> <Export-Package>org.jsoup.*</Export-Package> - <Import-Package>javax.annotation;version=!,javax.annotation.meta;version=!,*</Import-Package> + <Import-Package>javax.annotation;version=!;resolution:=optional,javax.annotation.meta;version=!;resolution:=optional,*</Import-Package> </instructions> </configuration> </plugin> From 80dfe593ec8184644e6332aafb9a209000f9d92a Mon Sep 17 00:00:00 2001 From: pushpagarwal <pushp.agarwal@gmail.com> Date: Thu, 26 Aug 2021 17:28:04 +0530 Subject: [PATCH 614/774] Update CombiningEvaluator.java --- src/main/java/org/jsoup/select/CombiningEvaluator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/select/CombiningEvaluator.java b/src/main/java/org/jsoup/select/CombiningEvaluator.java index 9648e942af..5a4f599947 100644 --- a/src/main/java/org/jsoup/select/CombiningEvaluator.java +++ b/src/main/java/org/jsoup/select/CombiningEvaluator.java @@ -11,7 +11,7 @@ /** * Base combining (and, or) evaluator. */ -abstract class CombiningEvaluator extends Evaluator { +public abstract class CombiningEvaluator extends Evaluator { final ArrayList<Evaluator> evaluators; int num = 0; @@ -39,7 +39,7 @@ void updateNumEvaluators() { num = evaluators.size(); } - static final class And extends CombiningEvaluator { + public static final class And extends CombiningEvaluator { And(Collection<Evaluator> evaluators) { super(evaluators); } @@ -64,7 +64,7 @@ public String toString() { } } - static final class Or extends CombiningEvaluator { + public static final class Or extends CombiningEvaluator { /** * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR. * @param evaluators initial OR clause (these are wrapped into an AND evaluator). From 440d82455a0c88f8472e901c1d8331d15be798bf Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 29 Aug 2021 14:26:59 +1000 Subject: [PATCH 615/774] Added support for tracking newlines in CharacterReader So that parse errors can be reported more intuitively. --- CHANGES | 3 + .../org/jsoup/parser/CharacterReader.java | 105 ++++++++++++++++ .../org/jsoup/parser/HtmlTreeBuilder.java | 2 +- .../java/org/jsoup/parser/ParseError.java | 27 +++- src/main/java/org/jsoup/parser/Parser.java | 1 + src/main/java/org/jsoup/parser/Tokeniser.java | 8 +- .../java/org/jsoup/parser/TreeBuilder.java | 3 +- .../org/jsoup/parser/CharacterReaderTest.java | 115 ++++++++++++++++++ .../java/org/jsoup/parser/HtmlParserTest.java | 24 ++-- src/test/resources/htmltests/large.html | 2 +- 10 files changed, 269 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 1adcd10122..e9e28c1e8d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.14.3 [PENDING] + * Improvement: added support in CharacterReader to track newlines, so that parse errors can be reported more + intuitively. + * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of java.annotation (used as a build-time dependency for nullability assertions). <https://github.com/jhy/jsoup/issues/1616> diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index ec80df96d8..af27c7aa72 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -3,10 +3,13 @@ import org.jsoup.UncheckedIOException; import org.jsoup.helper.Validate; +import javax.annotation.Nullable; import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Locale; /** @@ -29,6 +32,9 @@ public final class CharacterReader { private static final int stringCacheSize = 512; private String[] stringCache = new String[stringCacheSize]; // holds reused strings in this doc, to lessen garbage + @Nullable private ArrayList<Integer> newlinePositions = null; // optionally track the pos() position of newlines - scans during bufferUp() + private int lineNumberOffset = 1; // line numbers start at 1; += newlinePosition[indexof(pos)] + public CharacterReader(Reader input, int sz) { Validate.notNull(input); Validate.isTrue(input.markSupported()); @@ -98,6 +104,7 @@ private void bufferUp() { } catch (IOException e) { throw new UncheckedIOException(e); } + scanBufferForNewlines(); } /** @@ -108,6 +115,104 @@ public int pos() { return readerPos + bufPos; } + /** + Enables or disables line number tracking. By default, will be <b>off</b>.Tracking line numbers improves the + legibility of parser error messages, for example. Tracking should be enabled before any content is read to be of + use. + + @param track set tracking on|off + @since 1.14.3 + */ + public void trackNewlines(boolean track) { + if (track && newlinePositions == null) { + newlinePositions = new ArrayList<>(maxBufferLen / 80); // rough guess of likely count + scanBufferForNewlines(); // first pass when enabled; subsequently called during bufferUp + } + else if (!track) + newlinePositions = null; + } + + /** + Check if the tracking of newlines is enabled. + @return the current newline tracking state + @since 1.14.3 + */ + public boolean isTrackNewlines() { + return newlinePositions != null; + } + + /** + Get the current line number (that the reader has consumed to). Starts at line #1. + @return the current line number, or 1 if line tracking is not enabled. + @since 1.14.3 + @see #trackNewlines(boolean) + */ + public int lineNumber() { + if (!isTrackNewlines()) + return 1; + + int i = lineNumIndex(); + if (i == -1) + return lineNumberOffset; // first line + if (i < 0) + return Math.abs(i) + lineNumberOffset - 1; + return i + lineNumberOffset + 1; + } + + /** + Get the current column number (that the reader has consumed to). Starts at column #1. + @return the current column number + @since 1.14.3 + @see #trackNewlines(boolean) + */ + int columnNumber() { + if (!isTrackNewlines()) + return pos() + 1; + + int i = lineNumIndex(); + if (i == -1) + return pos() + 1; + if (i < 0) + i = Math.abs(i) - 2; + return pos() - newlinePositions.get(i) + 1; + } + + /** + Get a formatted string representing the current line and cursor positions. E.g. <code>5:10</code> indicating line + number 5 and column number 10. + @return line:col position + @since 1.14.3 + @see #trackNewlines(boolean) + */ + String cursorPos() { + return lineNumber() + ":" + columnNumber(); + } + + private int lineNumIndex() { + if (!isTrackNewlines()) return 0; + return Collections.binarySearch(newlinePositions, pos()); + } + + /** + Scans the buffer for newline position, and tracks their location in newlinePositions. + */ + private void scanBufferForNewlines() { + if (!isTrackNewlines()) + return; + + lineNumberOffset += newlinePositions.size(); + int lastPos = newlinePositions.size() > 0 ? newlinePositions.get(newlinePositions.size() -1) : -1; + newlinePositions.clear(); + if (lastPos != -1) { + newlinePositions.add(lastPos); // roll the last pos to first, for cursor num after buffer + lineNumberOffset--; // as this takes a position + } + for (int i = bufPos; i < bufLength; i++) { + if (charBuf[i] == '\n') + newlinePositions.add(1 + readerPos + i); + } + } + /** * Tests if all the content has been read. * @return true if nothing left to read. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index b276858a83..cdf1b797e2 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -204,7 +204,7 @@ boolean isFragmentParsing() { void error(HtmlTreeBuilderState state) { if (parser.getErrors().canAddError()) - parser.getErrors().add(new ParseError(reader.pos(), "Unexpected token [%s] when in state [%s]", currentToken.tokenType(), state)); + parser.getErrors().add(new ParseError(reader, "Unexpected token [%s] when in state [%s]", currentToken.tokenType(), state)); } Element insert(final Token.StartTag startTag) { diff --git a/src/main/java/org/jsoup/parser/ParseError.java b/src/main/java/org/jsoup/parser/ParseError.java index dfa090051b..feccea6800 100644 --- a/src/main/java/org/jsoup/parser/ParseError.java +++ b/src/main/java/org/jsoup/parser/ParseError.java @@ -5,16 +5,31 @@ */ public class ParseError { private int pos; + private String cursorPos; private String errorMsg; + ParseError(CharacterReader reader, String errorMsg) { + pos = reader.pos(); + cursorPos = reader.cursorPos(); + this.errorMsg = errorMsg; + } + + ParseError(CharacterReader reader, String errorFormat, Object... args) { + pos = reader.pos(); + cursorPos = reader.cursorPos(); + this.errorMsg = String.format(errorFormat, args); + } + ParseError(int pos, String errorMsg) { this.pos = pos; + cursorPos = String.valueOf(pos); this.errorMsg = errorMsg; } ParseError(int pos, String errorFormat, Object... args) { - this.errorMsg = String.format(errorFormat, args); this.pos = pos; + cursorPos = String.valueOf(pos); + this.errorMsg = String.format(errorFormat, args); } /** @@ -33,8 +48,16 @@ public int getPosition() { return pos; } + /** + Get the formatted line:column cursor position where the error occured. + @return line:number cursor position + */ + public String getCursorPos() { + return cursorPos; + } + @Override public String toString() { - return pos + ": " + errorMsg; + return "<" + cursorPos + ">: " + errorMsg; } } diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index 8ff9667e44..efc5f3c449 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -93,6 +93,7 @@ public Parser setTrackErrors(int maxErrors) { /** * Retrieve the parse errors, if any, from the last parse. * @return list of parse errors, up to the size of the maximum errors tracked. + * @see #setTrackErrors(int) */ public ParseErrorList getErrors() { return errors; diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 05a95c8cdb..68de3488b6 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -276,22 +276,22 @@ String appropriateEndTagName() { void error(TokeniserState state) { if (errors.canAddError()) - errors.add(new ParseError(reader.pos(), "Unexpected character '%s' in input state [%s]", reader.current(), state)); + errors.add(new ParseError(reader, "Unexpected character '%s' in input state [%s]", reader.current(), state)); } void eofError(TokeniserState state) { if (errors.canAddError()) - errors.add(new ParseError(reader.pos(), "Unexpectedly reached end of file (EOF) in input state [%s]", state)); + errors.add(new ParseError(reader, "Unexpectedly reached end of file (EOF) in input state [%s]", state)); } private void characterReferenceError(String message) { if (errors.canAddError()) - errors.add(new ParseError(reader.pos(), "Invalid character reference: %s", message)); + errors.add(new ParseError(reader, "Invalid character reference: %s", message)); } void error(String errorMsg) { if (errors.canAddError()) - errors.add(new ParseError(reader.pos(), errorMsg)); + errors.add(new ParseError(reader, errorMsg)); } boolean currentNodeInHtmlNS() { diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 42617f5201..a27ff35d2b 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -40,6 +40,7 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { this.parser = parser; settings = parser.settings(); reader = new CharacterReader(input); + reader.trackNewlines(parser.isTrackErrors()); // when tracking errors, enable newline tracking for better error reports currentToken = null; tokeniser = new Tokeniser(reader, parser.getErrors()); stack = new ArrayList<>(32); @@ -139,7 +140,7 @@ protected boolean currentElementIs(String normalName) { protected void error(String msg) { ParseErrorList errors = parser.getErrors(); if (errors.canAddError()) - errors.add(new ParseError(reader.pos(), msg)); + errors.add(new ParseError(reader, msg)); } /** diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index ba34d47a22..a362b1e648 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -1,8 +1,10 @@ package org.jsoup.parser; +import org.jsoup.integration.ParseTest; import org.junit.jupiter.api.Test; import java.io.BufferedReader; +import java.io.IOException; import java.io.StringReader; import static org.junit.jupiter.api.Assertions.*; @@ -336,4 +338,117 @@ public void notEmptyAtBufferSplitPoint() { assertTrue(r.isEmpty()); } + @Test public void canEnableAndDisableLineNumberTracking() { + CharacterReader reader = new CharacterReader("Hello!"); + assertFalse(reader.isTrackNewlines()); + reader.trackNewlines(true); + assertTrue(reader.isTrackNewlines()); + reader.trackNewlines(false); + assertFalse(reader.isTrackNewlines()); + } + + @Test public void canTrackNewlines() { + StringBuilder builder = new StringBuilder(); + builder.append("<foo>\n<bar>\n<qux>\n"); + while (builder.length() < maxBufferLen) + builder.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + builder.append("[foo]\n[bar]"); + String content = builder.toString(); + + CharacterReader noTrack = new CharacterReader(content); + assertFalse(noTrack.isTrackNewlines()); + CharacterReader track = new CharacterReader(content); + track.trackNewlines(true); + assertTrue(track.isTrackNewlines()); + + // check that no tracking works as expected (pos is 0 indexed, line number stays at 1, col is pos+1) + assertEquals(0, noTrack.pos()); + assertEquals(1, noTrack.lineNumber()); + assertEquals(1, noTrack.columnNumber()); + noTrack.consumeTo("<qux>"); + assertEquals(12, noTrack.pos()); + assertEquals(1, noTrack.lineNumber()); + assertEquals(13, noTrack.columnNumber()); + assertEquals("1:13", noTrack.cursorPos()); + // get over the buffer + while (!noTrack.matches("[foo]")) + noTrack.consumeTo("[foo]"); + assertEquals(32778, noTrack.pos()); + assertEquals(1, noTrack.lineNumber()); + assertEquals(noTrack.pos()+1, noTrack.columnNumber()); + assertEquals("1:32779", noTrack.cursorPos()); + + // and the line numbers: "<foo>\n<bar>\n<qux>\n" + assertEquals(0, track.pos()); + assertEquals(1, track.lineNumber()); + assertEquals(1, track.columnNumber()); + + track.consumeTo('\n'); + assertEquals(1, track.lineNumber()); + assertEquals(6, track.columnNumber()); + track.consume(); + assertEquals(2, track.lineNumber()); + assertEquals(1, track.columnNumber()); + + assertEquals("<bar>", track.consumeTo('\n')); + assertEquals(2, track.lineNumber()); + assertEquals(6, track.columnNumber()); + + assertEquals("\n", track.consumeTo("<qux>")); + assertEquals(12, track.pos()); + assertEquals(3, track.lineNumber()); + assertEquals(1, track.columnNumber()); + assertEquals("3:1", track.cursorPos()); + assertEquals("<qux>", track.consumeTo('\n')); + assertEquals("3:6", track.cursorPos()); + // get over the buffer + while (!track.matches("[foo]")) + track.consumeTo("[foo]"); + assertEquals(32778, track.pos()); + assertEquals(4, track.lineNumber()); + assertEquals(32761, track.columnNumber()); + assertEquals("4:32761", track.cursorPos()); + track.consumeTo('\n'); + assertEquals("4:32766", track.cursorPos()); + + track.consumeTo("[bar]"); + assertEquals(5, track.lineNumber()); + assertEquals("5:1", track.cursorPos()); + track.consumeToEnd(); + assertEquals("5:6", track.cursorPos()); + } + + @Test public void countsColumnsOverBufferWhenNoNewlines() { + StringBuilder builder = new StringBuilder(); + while (builder.length() < maxBufferLen * 4) + builder.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + String content = builder.toString(); + CharacterReader reader = new CharacterReader(content); + reader.trackNewlines(true); + + assertEquals("1:1", reader.cursorPos()); + while (!reader.isEmpty()) + reader.consume(); + assertEquals(131096, reader.pos()); + assertEquals(reader.pos() + 1, reader.columnNumber()); + assertEquals(1, reader.lineNumber()); + } + + @Test public void linenumbersAgreeWithEditor() throws IOException { + String content = ParseTest.getFileAsString(ParseTest.getFile("/htmltests/large.html")); + CharacterReader reader = new CharacterReader(content); + reader.trackNewlines(true); + + String scan = "<p>VESTIBULUM"; // near the end of the file + while (!reader.matches(scan)) + reader.consumeTo(scan); + + assertEquals(280218, reader.pos()); + assertEquals(1002, reader.lineNumber()); + assertEquals(1, reader.columnNumber()); + reader.consumeTo(' '); + assertEquals(1002, reader.lineNumber()); + assertEquals(14, reader.columnNumber()); + } + } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 2d27e53114..3177a334e2 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -835,29 +835,29 @@ public class HtmlParserTest { } @Test public void tracksErrorsWhenRequested() { - String html = "<p>One</p href='no'><!DOCTYPE html>&arrgh;<font /><br /><foo"; + String html = "<p>One</p href='no'>\n<!DOCTYPE html>\n&arrgh;<font /><br /><foo"; Parser parser = Parser.htmlParser().setTrackErrors(500); Document doc = Jsoup.parse(html, "http://example.com", parser); List<ParseError> errors = parser.getErrors(); assertEquals(5, errors.size()); - assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); - assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("36: Invalid character reference: invalid named reference", errors.get(2).toString()); - assertEquals("50: Tag cannot be self closing; not a void tag", errors.get(3).toString()); - assertEquals("61: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(4).toString()); + assertEquals("<1:21>: Attributes incorrectly present on end tag", errors.get(0).toString()); + assertEquals("<2:16>: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); + assertEquals("<3:2>: Invalid character reference: invalid named reference", errors.get(2).toString()); + assertEquals("<3:16>: Tag cannot be self closing; not a void tag", errors.get(3).toString()); + assertEquals("<3:27>: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(4).toString()); } @Test public void tracksLimitedErrorsWhenRequested() { - String html = "<p>One</p href='no'><!DOCTYPE html>&arrgh;<font /><br /><foo"; + String html = "<p>One</p href='no'>\n<!DOCTYPE html>\n&arrgh;<font /><br /><foo"; Parser parser = Parser.htmlParser().setTrackErrors(3); Document doc = parser.parseInput(html, "http://example.com"); List<ParseError> errors = parser.getErrors(); assertEquals(3, errors.size()); - assertEquals("20: Attributes incorrectly present on end tag", errors.get(0).toString()); - assertEquals("35: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("36: Invalid character reference: invalid named reference", errors.get(2).toString()); + assertEquals("<1:21>: Attributes incorrectly present on end tag", errors.get(0).toString()); + assertEquals("<2:16>: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); + assertEquals("<3:2>: Invalid character reference: invalid named reference", errors.get(2).toString()); } @Test public void noErrorsByDefault() { @@ -1172,11 +1172,11 @@ public void testInvalidTableContents() throws IOException { } @Test public void selfClosingOnNonvoidIsError() { - String html = "<p>test</p><div /><div>Two</div>"; + String html = "<p>test</p>\n\n<div /><div>Two</div>"; Parser parser = Parser.htmlParser().setTrackErrors(5); parser.parseInput(html, ""); assertEquals(1, parser.getErrors().size()); - assertEquals("18: Tag cannot be self closing; not a void tag", parser.getErrors().get(0).toString()); + assertEquals("<3:8>: Tag cannot be self closing; not a void tag", parser.getErrors().get(0).toString()); assertFalse(Jsoup.isValid(html, Safelist.relaxed())); String clean = Jsoup.clean(html, Safelist.relaxed()); diff --git a/src/test/resources/htmltests/large.html b/src/test/resources/htmltests/large.html index 797afecbff..bdf04dd320 100644 --- a/src/test/resources/htmltests/large.html +++ b/src/test/resources/htmltests/large.html @@ -999,6 +999,6 @@ <p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> -<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Proin quam</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> +<p>VESTIBULUM tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Proin quam</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> </body></html> From 0b739dbdfb8d26426ed502910e2712ca26a8605e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 29 Aug 2021 14:33:12 +1000 Subject: [PATCH 616/774] Changelog PR URL --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index e9e28c1e8d..bb18b2fcee 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ jsoup changelog *** Release 1.14.3 [PENDING] * Improvement: added support in CharacterReader to track newlines, so that parse errors can be reported more intuitively. + <https://github.com/jhy/jsoup/pull/1624> * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of java.annotation (used as a build-time dependency for nullability assertions). From d0ec68fe52b296dece11f1abf36dc59979a66e9e Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 29 Aug 2021 18:03:57 +1000 Subject: [PATCH 617/774] Improved parse error messages Now shows the specific tag / attribute / character reference in error. --- .../org/jsoup/parser/HtmlTreeBuilder.java | 4 ++-- src/main/java/org/jsoup/parser/Tokeniser.java | 21 ++++++++++------- .../java/org/jsoup/parser/TreeBuilder.java | 11 ++++++++- .../java/org/jsoup/parser/HtmlParserTest.java | 23 +++++++++++-------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index cdf1b797e2..116db773c7 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -212,7 +212,7 @@ Element insert(final Token.StartTag startTag) { if (startTag.hasAttributes() && !startTag.attributes.isEmpty()) { int dupes = startTag.attributes.deduplicate(settings); if (dupes > 0) { - error("Duplicate attribute"); + error("Dropped duplicate attribute(s) in tag [%s]", startTag.normalName); } } @@ -249,7 +249,7 @@ Element insertEmpty(Token.StartTag startTag) { if (startTag.isSelfClosing()) { if (tag.isKnownTag()) { if (!tag.isEmpty()) - tokeniser.error("Tag cannot be self closing; not a void tag"); + tokeniser.error("Tag [%s] cannot be self closing; not a void tag", tag.normalName()); } else // unknown tag, remember this is self closing for output tag.setSelfClosing(); diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 68de3488b6..d8d9a7b9d3 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -87,7 +87,7 @@ void emit(Token token) { } else if (token.type == Token.TokenType.EndTag) { Token.EndTag endTag = (Token.EndTag) token; if (endTag.hasAttributes()) - error("Attributes incorrectly present on end tag"); + error("Attributes incorrectly present on end tag [/%s]", endTag.normalName()); } } @@ -174,7 +174,7 @@ void advanceTransition(TokeniserState state) { reader.unmark(); if (!reader.matchConsume(";")) - characterReferenceError("missing semicolon"); // missing semi + characterReferenceError("missing semicolon on [&#%s]", numRef); // missing semi int charval = -1; try { int base = isHexMode ? 16 : 10; @@ -182,12 +182,12 @@ void advanceTransition(TokeniserState state) { } catch (NumberFormatException ignored) { } // skip if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) { - characterReferenceError("character outside of valid range"); + characterReferenceError("character [%s] outside of valid range", charval); codeRef[0] = replacementChar; } else { // fix illegal unicode characters to match browser behavior if (charval >= win1252ExtensionsStart && charval < win1252ExtensionsStart + win1252Extensions.length) { - characterReferenceError("character is not a valid unicode code point"); + characterReferenceError("character [%s] is not a valid unicode code point", charval); charval = win1252Extensions[charval - win1252ExtensionsStart]; } @@ -206,7 +206,7 @@ void advanceTransition(TokeniserState state) { if (!found) { reader.rewindToMark(); if (looksLegit) // named with semicolon - characterReferenceError("invalid named reference"); + characterReferenceError("invalid named reference [%s]", nameRef); return null; } if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny('=', '-', '_'))) { @@ -217,7 +217,7 @@ void advanceTransition(TokeniserState state) { reader.unmark(); if (!reader.matchConsume(";")) - characterReferenceError("missing semicolon"); // missing semi + characterReferenceError("missing semicolon on [&%s]", nameRef); // missing semi int numChars = Entities.codepointsForName(nameRef, multipointHolder); if (numChars == 1) { codeRef[0] = multipointHolder[0]; @@ -284,9 +284,9 @@ void eofError(TokeniserState state) { errors.add(new ParseError(reader, "Unexpectedly reached end of file (EOF) in input state [%s]", state)); } - private void characterReferenceError(String message) { + private void characterReferenceError(String message, Object... args) { if (errors.canAddError()) - errors.add(new ParseError(reader, "Invalid character reference: %s", message)); + errors.add(new ParseError(reader, String.format("Invalid character reference: " + message, args))); } void error(String errorMsg) { @@ -294,6 +294,11 @@ void error(String errorMsg) { errors.add(new ParseError(reader, errorMsg)); } + void error(String errorMsg, Object... args) { + if (errors.canAddError()) + errors.add(new ParseError(reader, errorMsg, args)); + } + boolean currentNodeInHtmlNS() { // todo: implement namespaces correctly return true; diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index a27ff35d2b..e21d70c586 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -138,9 +138,18 @@ protected boolean currentElementIs(String normalName) { * @param msg error message */ protected void error(String msg) { + error(msg, (Object[]) null); + } + + /** + * If the parser is tracking errors, add an error at the current position. + * @param msg error message template + * @param args template arguments + */ + protected void error(String msg, Object... args) { ParseErrorList errors = parser.getErrors(); if (errors.canAddError()) - errors.add(new ParseError(reader, msg)); + errors.add(new ParseError(reader, msg, args)); } /** diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 3177a334e2..035aa3f056 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -55,7 +55,7 @@ public class HtmlParserTest { assertEquals("<p one=\"One\" two=\"two\">Text</p>", p.outerHtml()); // normalized names due to lower casing assertEquals(1, parser.getErrors().size()); - assertEquals("Duplicate attribute", parser.getErrors().get(0).getErrorMessage()); + assertEquals("Dropped duplicate attribute(s) in tag [p]", parser.getErrors().get(0).getErrorMessage()); } @Test public void retainsAttributesOfDifferentCaseIfSensitive() { @@ -835,17 +835,20 @@ public class HtmlParserTest { } @Test public void tracksErrorsWhenRequested() { - String html = "<p>One</p href='no'>\n<!DOCTYPE html>\n&arrgh;<font /><br /><foo"; + String html = "<p>One</p href='no'>\n<!DOCTYPE html>\n&arrgh;<font />&#33 &amp &#xD800;<br /><foo"; Parser parser = Parser.htmlParser().setTrackErrors(500); Document doc = Jsoup.parse(html, "http://example.com", parser); List<ParseError> errors = parser.getErrors(); - assertEquals(5, errors.size()); - assertEquals("<1:21>: Attributes incorrectly present on end tag", errors.get(0).toString()); + assertEquals(8, errors.size()); + assertEquals("<1:21>: Attributes incorrectly present on end tag [/p]", errors.get(0).toString()); assertEquals("<2:16>: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("<3:2>: Invalid character reference: invalid named reference", errors.get(2).toString()); - assertEquals("<3:16>: Tag cannot be self closing; not a void tag", errors.get(3).toString()); - assertEquals("<3:27>: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(4).toString()); + assertEquals("<3:2>: Invalid character reference: invalid named reference [arrgh]", errors.get(2).toString()); + assertEquals("<3:16>: Tag [font] cannot be self closing; not a void tag", errors.get(3).toString()); + assertEquals("<3:20>: Invalid character reference: missing semicolon on [&#33]", errors.get(4).toString()); + assertEquals("<3:25>: Invalid character reference: missing semicolon on [&amp]", errors.get(5).toString()); + assertEquals("<3:34>: Invalid character reference: character [55296] outside of valid range", errors.get(6).toString()); + assertEquals("<3:45>: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(7).toString()); } @Test public void tracksLimitedErrorsWhenRequested() { @@ -855,9 +858,9 @@ public class HtmlParserTest { List<ParseError> errors = parser.getErrors(); assertEquals(3, errors.size()); - assertEquals("<1:21>: Attributes incorrectly present on end tag", errors.get(0).toString()); + assertEquals("<1:21>: Attributes incorrectly present on end tag [/p]", errors.get(0).toString()); assertEquals("<2:16>: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); - assertEquals("<3:2>: Invalid character reference: invalid named reference", errors.get(2).toString()); + assertEquals("<3:2>: Invalid character reference: invalid named reference [arrgh]", errors.get(2).toString()); } @Test public void noErrorsByDefault() { @@ -1176,7 +1179,7 @@ public void testInvalidTableContents() throws IOException { Parser parser = Parser.htmlParser().setTrackErrors(5); parser.parseInput(html, ""); assertEquals(1, parser.getErrors().size()); - assertEquals("<3:8>: Tag cannot be self closing; not a void tag", parser.getErrors().get(0).toString()); + assertEquals("<3:8>: Tag [div] cannot be self closing; not a void tag", parser.getErrors().get(0).toString()); assertFalse(Jsoup.isValid(html, Safelist.relaxed())); String clean = Jsoup.clean(html, Safelist.relaxed()); From d7d10ad142ee2b0fc8089e063eb382511b3ca6e9 Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Tue, 31 Aug 2021 23:24:50 +0000 Subject: [PATCH 618/774] New GH Actions caching option (#1625) * Move to new caching options. * EOL Java 15 build removed. --- .github/workflows/build.yml | 14 ++++---------- .github/workflows/codeql.yml | 11 +++-------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1dd5333cee..7da03a0628 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time - java: [8, 11, 15, 16] + java: [8, 11, 16] fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: @@ -20,17 +20,11 @@ jobs: uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} - - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-java-${{ matrix.java }}-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + distribution: 'temurin' + cache: 'maven' - name: Maven Compile run: mvn -X compile -B --file pom.xml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f387b2f721..0b615653a9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,16 +16,11 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11 - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-java-codeql-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + distribution: 'temurin' + cache: 'maven' - name: CodeQL Initialization uses: github/codeql-action/init@v1 with: From 880720e9118936174187c2dcb7fb747c38a12874 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Thu, 2 Sep 2021 22:25:37 +1000 Subject: [PATCH 619/774] Fixed generateImpliedEndTags When no exlude tag was provided, the search was incorrect. And so optional closers like <p> would incorrectly add errors. --- CHANGES | 5 +++++ .../org/jsoup/parser/HtmlTreeBuilder.java | 11 +++++++---- src/main/java/org/jsoup/parser/Token.java | 5 +++++ .../java/org/jsoup/parser/HtmlParserTest.java | 19 ++++++++++++++----- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index bb18b2fcee..ce87709f0a 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,11 @@ jsoup changelog intuitively. <https://github.com/jhy/jsoup/pull/1624> + * Improvement: tracked parse errors now have more details, including the erroneous token, to help clarify the errors. + + * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing + optional closing tags. + * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of java.annotation (used as a build-time dependency for nullability assertions). <https://github.com/jhy/jsoup/issues/1616> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 116db773c7..8c7c8ce294 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -31,7 +31,7 @@ public class HtmlTreeBuilder extends TreeBuilder { static final String[] TagSearchButton = new String[]{"button"}; static final String[] TagSearchTableScope = new String[]{"html", "table"}; static final String[] TagSearchSelectScope = new String[]{"optgroup", "option"}; - static final String[] TagSearchEndTags = new String[]{"dd", "dt", "li", "optgroup", "option", "p", "rp", "rt"}; + static final String[] TagSearchEndTags = new String[]{"dd", "dt", "li", "optgroup", "option", "p", "rb", "rp", "rt", "rtc"}; static final String[] TagSearchSpecial = new String[]{"address", "applet", "area", "article", "aside", "base", "basefont", "bgsound", "blockquote", "body", "br", "button", "caption", "center", "col", "colgroup", "command", "dd", "details", "dir", "div", "dl", "dt", "embed", "fieldset", "figcaption", "figure", "footer", "form", @@ -204,7 +204,8 @@ boolean isFragmentParsing() { void error(HtmlTreeBuilderState state) { if (parser.getErrors().canAddError()) - parser.getErrors().add(new ParseError(reader, "Unexpected token [%s] when in state [%s]", currentToken.tokenType(), state)); + parser.getErrors().add(new ParseError(reader, "Unexpected %s token [%s] when in state [%s]", + currentToken.tokenType(), currentToken, state)); } Element insert(final Token.StartTag startTag) { @@ -606,9 +607,11 @@ List<String> getPendingTableCharacters() { process, then the UA must perform the above steps as if that element was not in the above list. */ void generateImpliedEndTags(String excludeTag) { - while ((excludeTag != null && !currentElementIs(excludeTag)) && - inSorted(currentElement().normalName(), TagSearchEndTags)) + while (inSorted(currentElement().normalName(), TagSearchEndTags)) { + if (excludeTag != null && currentElementIs(excludeTag)) + break; pop(); + } } void generateImpliedEndTags() { diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 3c5bf100b2..9037b8633b 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -72,6 +72,11 @@ public String getSystemIdentifier() { public boolean isForceQuirks() { return forceQuirks; } + + @Override + public String toString() { + return "<!doctype " + getName() + ">"; + } } static abstract class Tag extends Token { diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 035aa3f056..33e5f9e9d1 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -835,20 +835,21 @@ public class HtmlParserTest { } @Test public void tracksErrorsWhenRequested() { - String html = "<p>One</p href='no'>\n<!DOCTYPE html>\n&arrgh;<font />&#33 &amp &#xD800;<br /><foo"; + String html = "<p>One</p href='no'>\n<!DOCTYPE html>\n&arrgh;<font />&#33 &amp &#xD800;<br /></div><foo"; Parser parser = Parser.htmlParser().setTrackErrors(500); Document doc = Jsoup.parse(html, "http://example.com", parser); List<ParseError> errors = parser.getErrors(); - assertEquals(8, errors.size()); + assertEquals(9, errors.size()); assertEquals("<1:21>: Attributes incorrectly present on end tag [/p]", errors.get(0).toString()); - assertEquals("<2:16>: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); + assertEquals("<2:16>: Unexpected Doctype token [<!doctype html>] when in state [InBody]", errors.get(1).toString()); assertEquals("<3:2>: Invalid character reference: invalid named reference [arrgh]", errors.get(2).toString()); assertEquals("<3:16>: Tag [font] cannot be self closing; not a void tag", errors.get(3).toString()); assertEquals("<3:20>: Invalid character reference: missing semicolon on [&#33]", errors.get(4).toString()); assertEquals("<3:25>: Invalid character reference: missing semicolon on [&amp]", errors.get(5).toString()); assertEquals("<3:34>: Invalid character reference: character [55296] outside of valid range", errors.get(6).toString()); - assertEquals("<3:45>: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(7).toString()); + assertEquals("<3:46>: Unexpected EndTag token [</div>] when in state [InBody]", errors.get(7).toString()); + assertEquals("<3:51>: Unexpectedly reached end of file (EOF) in input state [TagName]", errors.get(8).toString()); } @Test public void tracksLimitedErrorsWhenRequested() { @@ -859,7 +860,7 @@ public class HtmlParserTest { List<ParseError> errors = parser.getErrors(); assertEquals(3, errors.size()); assertEquals("<1:21>: Attributes incorrectly present on end tag [/p]", errors.get(0).toString()); - assertEquals("<2:16>: Unexpected token [Doctype] when in state [InBody]", errors.get(1).toString()); + assertEquals("<2:16>: Unexpected Doctype token [<!doctype html>] when in state [InBody]", errors.get(1).toString()); assertEquals("<3:2>: Invalid character reference: invalid named reference [arrgh]", errors.get(2).toString()); } @@ -872,6 +873,14 @@ public class HtmlParserTest { assertEquals(0, errors.size()); } + @Test public void optionalPClosersAreNotErrors() { + String html = "<body><div><p>One<p>Two</div></body>"; + Parser parser = Parser.htmlParser().setTrackErrors(128); + Document doc = Jsoup.parse(html, "", parser); + ParseErrorList errors = parser.getErrors(); + assertEquals(0, errors.size()); + } + @Test public void handlesCommentsInTable() { String html = "<table><tr><td>text</td><!-- Comment --></tr></table>"; Document node = Jsoup.parseBodyFragment(html); From f7ef7c3dc986e420891b5d0d58feba27019c5974 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Tue, 7 Sep 2021 20:18:40 +1000 Subject: [PATCH 620/774] Added Nullable annotations for equals() Fixed #1628 --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/main/java/org/jsoup/nodes/Attributes.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index ce87709f0a..55a6800402 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,9 @@ jsoup changelog dependency for nullability assertions). <https://github.com/jhy/jsoup/issues/1616> + * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods. + <https://github.com/jhy/jsoup/issues/1628> + *** Release 1.14.2 [2021-Aug-15] * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. <https://github.com/jhy/jsoup/pull/1536> diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index f31124c99a..1e0f77e8c4 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -188,7 +188,7 @@ protected static boolean isBooleanAttribute(final String key) { } @Override - public boolean equals(Object o) { // note parent not considered + public boolean equals(@Nullable Object o) { // note parent not considered if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Attribute attribute = (Attribute) o; diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 61d9cbd74a..6b3e8cd0b6 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -376,7 +376,7 @@ public String toString() { * @return if both sets of attributes have the same content */ @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 37d2a59819..9fe7417159 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -691,7 +691,7 @@ protected void indent(Appendable accum, int depth, Document.OutputSettings out) * @see Node#hasSameValue(Object) */ @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { // implemented just so that javadoc is clear this is an identity test return this == o; } From c283a8dd0c5f0f5e726fccaef329ee217e98b1b2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 13 Sep 2021 14:28:43 +1000 Subject: [PATCH 621/774] Native XPath support in jsoup (#1629) Bringing in as a beta feature in 1.14.3 --- CHANGES | 3 + pom.xml | 1 - src/main/java/org/jsoup/Jsoup.java | 18 +- src/main/java/org/jsoup/helper/W3CDom.java | 124 ++++++++++-- src/main/java/org/jsoup/nodes/Element.java | 140 ++++++++------ src/main/java/org/jsoup/nodes/NodeUtils.java | 20 ++ src/test/java/org/jsoup/TextUtil.java | 11 ++ .../java/org/jsoup/helper/W3CDomTest.java | 46 ++++- src/test/java/org/jsoup/select/XpathTest.java | 177 ++++++++++++++++++ 9 files changed, 448 insertions(+), 92 deletions(-) create mode 100644 src/test/java/org/jsoup/select/XpathTest.java diff --git a/CHANGES b/CHANGES index 55a6800402..9d1c77ba77 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.14.3 [PENDING] + * Improvement: added native XPath support in Element#selectXpath(String) + <https://github.com/jhy/jsoup/pull/1629> + * Improvement: added support in CharacterReader to track newlines, so that parse errors can be reported more intuitively. <https://github.com/jhy/jsoup/pull/1624> diff --git a/pom.xml b/pom.xml index e58b8a1d9b..249ed7a892 100644 --- a/pom.xml +++ b/pom.xml @@ -344,7 +344,6 @@ <version>3.0.2</version> <scope>provided</scope> </dependency> - </dependencies> <dependencyManagement> diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 1e0a3d8d97..1541b03afb 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -49,8 +49,22 @@ public static Document parse(String html, String baseUri, Parser parser) { } /** - Parse HTML into a Document. As no base URI is specified, absolute URL detection relies on the HTML including a - {@code <base href>} tag. + Parse HTML into a Document, using the provided Parser. You can provide an alternate parser, such as a simple XML + (non-HTML) parser. As no base URI is specified, absolute URL resolution, if required, relies on the HTML including + a {@code <base href>} tag. + + @param html HTML to parse + before the HTML declares a {@code <base href>} tag. + @param parser alternate {@link Parser#xmlParser() parser} to use. + @return sane HTML + */ + public static Document parse(String html, Parser parser) { + return parser.parseInput(html, ""); + } + + /** + Parse HTML into a Document. As no base URI is specified, absolute URL resolution, if required, relies on the HTML + including a {@code <base href>} tag. @param html HTML to parse @return sane HTML diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index d3dae0c5e5..11a595df19 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -3,8 +3,10 @@ import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; +import org.jsoup.select.Elements; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; +import org.jsoup.select.Selector; import org.w3c.dom.Comment; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; @@ -12,6 +14,7 @@ import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.w3c.dom.Text; import javax.annotation.Nullable; @@ -24,6 +27,11 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFactoryConfigurationException; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; @@ -38,6 +46,15 @@ * for integration with toolsets that use the W3C DOM. */ public class W3CDom { + /** For W3C Documents created by this class, this property is set on each node to link back to the original jsoup node. */ + public static final String SourceProperty = "jsoupSource"; + + /** + To get support for XPath versions > 1, set this property to the classname of an alternate XPathFactory + implementation (e.g. <code>net.sf.saxon.xpath.XPathFactoryImpl</code>). + */ + public static final String XPathFactoryProperty = "javax.xml.xpath.XPathFactory:jsoup"; + protected DocumentBuilderFactory factory; public W3CDom() { @@ -46,7 +63,7 @@ public W3CDom() { } /** - * Converts a jsoup DOM to a W3C DOM + * Converts a jsoup DOM to a W3C DOM. * * @param in jsoup Document * @return W3C Document @@ -130,12 +147,27 @@ private static HashMap<String, String> methodMap(String method) { } /** - * Convert a jsoup Document to a W3C Document. + * Convert a jsoup Document to a W3C Document. The created nodes will link back to the original + * jsoup nodes in the user property {@link #SourceProperty} (but after conversion, changes on one side will not + * flow to the other). * * @param in jsoup doc - * @return w3c doc + * @return a W3C DOM Document representing the jsoup Document or Element contents. */ public Document fromJsoup(org.jsoup.nodes.Document in) { + // just method API backcompat + return fromJsoup((org.jsoup.nodes.Element) in); + } + + /** + * Convert a jsoup Element to a W3C Document. The created nodes will link back to the original + * jsoup nodes in the user property {@link #SourceProperty} (but after conversion, changes on one side will not + * flow to the other). + * + * @param in jsoup element or doc + * @return a W3C DOM Document representing the jsoup Document or Element contents. + */ + public Document fromJsoup(org.jsoup.nodes.Element in) { Validate.notNull(in); DocumentBuilder builder; try { @@ -144,7 +176,8 @@ public Document fromJsoup(org.jsoup.nodes.Document in) { Document out; out = builder.newDocument(); - org.jsoup.nodes.DocumentType doctype = in.documentType(); + org.jsoup.nodes.Document inDoc = in.ownerDocument(); + org.jsoup.nodes.DocumentType doctype = inDoc != null ? inDoc.documentType() : null; if (doctype != null) { org.w3c.dom.DocumentType documentType = impl.createDocumentType(doctype.name(), doctype.publicId(), doctype.systemId()); out.appendChild(documentType); @@ -159,21 +192,70 @@ public Document fromJsoup(org.jsoup.nodes.Document in) { } /** - * Converts a jsoup document into the provided W3C Document. If required, you can set options on the output document - * before converting. + * Converts a jsoup document into the provided W3C Document. If required, you can set options on the output + * document before converting. * * @param in jsoup doc * @param out w3c doc - * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Document) + * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Element) */ public void convert(org.jsoup.nodes.Document in, Document out) { - if (!StringUtil.isBlank(in.location())) - out.setDocumentURI(in.location()); + // just provides method API backcompat + convert((org.jsoup.nodes.Element) in, out); + } - org.jsoup.nodes.Element rootEl = in.child(0); // skip the #root node + /** + * Converts a jsoup element into the provided W3C Document. If required, you can set options on the output + * document before converting. + * + * @param in jsoup element + * @param out w3c doc + * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Element) + */ + public void convert(org.jsoup.nodes.Element in, Document out) { + org.jsoup.nodes.Document inDoc = in.ownerDocument(); + if (inDoc != null) { + if (!StringUtil.isBlank(inDoc.location())) + out.setDocumentURI(inDoc.location()); + } + + org.jsoup.nodes.Element rootEl = in instanceof org.jsoup.nodes.Document ? in.child(0) : in; // skip the #root node if a Document NodeTraversor.traverse(new W3CBuilder(out), rootEl); } + public NodeList selectXpath(String xpath, Document doc) { + Validate.notEmpty(xpath); + Validate.notNull(doc); + + NodeList nodeList; + try { + // if there is a configured XPath factory, use that instead of the Java base impl: + String property = System.getProperty(XPathFactoryProperty); + final XPathFactory xPathFactory = property != null ? + XPathFactory.newInstance("jsoup") : + XPathFactory.newInstance(); + + XPathExpression expression = xPathFactory.newXPath().compile(xpath); + nodeList = (NodeList) expression.evaluate(doc, XPathConstants.NODESET); // love the strong typing here /s + Validate.notNull(nodeList); + } catch (XPathExpressionException | XPathFactoryConfigurationException e) { + throw new Selector.SelectorParseException("Could not evaluate XPath query [%s]: %s", xpath, e.getMessage()); + } + return nodeList; + } + + public Elements sourceElements(NodeList nodeList) { + Elements els = new Elements(); + for (int i = 0; i < nodeList.getLength(); i++) { + org.w3c.dom.Node node = nodeList.item(i); + Object source = node.getUserData(W3CDom.SourceProperty); + if (source instanceof org.jsoup.nodes.Element) + els.add((org.jsoup.nodes.Element) source); + } + + return els; + } + /** * Serialize a W3C document to a String. The output format will be XML or HTML depending on the content of the doc. * @@ -211,7 +293,7 @@ public void head(org.jsoup.nodes.Node source, int depth) { String namespace = namespacesStack.peek().get(prefix); String tagName = sourceEl.tagName(); - /* Tag names in XML are quite, but less, permissive than HTML. Rather than reimplement the validation, + /* Tag names in XML are quite permissive, but less permissive than HTML. Rather than reimplement the validation, we just try to use it as-is. If it fails, insert as a text node instead. We don't try to normalize the tagname to something safe, because that isn't going to be meaningful downstream. This seems(?) to be how browsers handle the situation, also. https://github.com/jhy/jsoup/issues/1093 */ @@ -220,32 +302,36 @@ public void head(org.jsoup.nodes.Node source, int depth) { doc.createElementNS("", tagName) : // doesn't have a real namespace defined doc.createElementNS(namespace, tagName); copyAttributes(sourceEl, el); - dest.appendChild(el); + append(el, sourceEl); dest = el; // descend } catch (DOMException e) { - dest.appendChild(doc.createTextNode("<" + tagName + ">")); + append(doc.createTextNode("<" + tagName + ">"), sourceEl); } } else if (source instanceof org.jsoup.nodes.TextNode) { org.jsoup.nodes.TextNode sourceText = (org.jsoup.nodes.TextNode) source; Text text = doc.createTextNode(sourceText.getWholeText()); - dest.appendChild(text); + append(text, sourceText); } else if (source instanceof org.jsoup.nodes.Comment) { org.jsoup.nodes.Comment sourceComment = (org.jsoup.nodes.Comment) source; Comment comment = doc.createComment(sourceComment.getData()); - dest.appendChild(comment); + append(comment, sourceComment); } else if (source instanceof org.jsoup.nodes.DataNode) { org.jsoup.nodes.DataNode sourceData = (org.jsoup.nodes.DataNode) source; Text node = doc.createTextNode(sourceData.getWholeData()); - dest.appendChild(node); + append(node, sourceData); } else { - // unhandled - // not that doctype is not handled here - rather it is used in the initial doc creation + // unhandled. note that doctype is not handled here - rather it is used in the initial doc creation } } + private void append(Node append, org.jsoup.nodes.Node source) { + append.setUserData(SourceProperty, source, null); + dest.appendChild(append); + } + public void tail(org.jsoup.nodes.Node source, int depth) { if (source instanceof org.jsoup.nodes.Element && dest.getParentNode() instanceof Element) { - dest = dest.getParentNode(); // undescend. cromulent. + dest = dest.getParentNode(); // undescend } namespacesStack.pop(); } diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 0a0180bcc4..c1e08190dc 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -34,9 +34,9 @@ /** * A HTML element consists of a tag name, attributes, and child nodes (including text nodes and * other elements). - * + * * From an Element, you can extract data, traverse the node graph, and manipulate the HTML. - * + * * @author Jonathan Hedley, jonathan@hedley.net */ @NonnullByDefault @@ -59,7 +59,7 @@ public Element(String tag) { /** * Create a new, standalone Element. (Standalone in that is has no parent.) - * + * * @param tag tag of this element * @param baseUri the base URI (optional, may be null to inherit from parent, or "" to clear parent's) * @param attributes initial attributes (optional, may be null) @@ -77,7 +77,7 @@ public Element(Tag tag, @Nullable String baseUri, @Nullable Attributes attribute /** * Create a new Element from a Tag and a base URI. - * + * * @param tag element tag * @param baseUri the base URI of this element. Optional, and will inherit from its parent, if any. * @see Tag#valueOf(String, ParseSettings) @@ -145,7 +145,7 @@ public String nodeName() { /** * Get the name of the tag for this element. E.g. {@code div}. If you are using {@link ParseSettings#preserveCase * case preserving parsing}, this will return the source's original case. - * + * * @return the tag name */ public String tagName() { @@ -168,7 +168,7 @@ public String normalName() { * * @param tagName new tag name for this element * @return this element, for chaining - * @see Elements#tagName(String) + * @see Elements#tagName(String) */ public Element tagName(String tagName) { Validate.notEmpty(tagName, "Tag name must not be empty."); @@ -178,17 +178,17 @@ public Element tagName(String tagName) { /** * Get the Tag for this element. - * + * * @return the tag object */ public Tag tag() { return tag; } - + /** * Test if this element is a block-level element. (E.g. {@code <div> == true} or an inline element * {@code <span> == false}). - * + * * @return true if block, false if not (and thus inline) */ public boolean isBlock() { @@ -197,7 +197,7 @@ public boolean isBlock() { /** * Get the {@code id} attribute of this element. - * + * * @return The id attribute, if present, or an empty string if not. */ public String id() { @@ -218,22 +218,22 @@ public Element id(String id) { /** * Set an attribute value on this element. If this element already has an attribute with the * key, its value is updated; otherwise, a new attribute is added. - * + * * @return this element */ public Element attr(String attributeKey, String attributeValue) { super.attr(attributeKey, attributeValue); return this; } - + /** * Set a boolean attribute value on this element. Setting to <code>true</code> sets the attribute value to "" and * marks the attribute as boolean so no value is written out. Setting to <code>false</code> removes the attribute * with the same key if it exists. - * + * * @param attributeKey the attribute key * @param attributeValue the attribute value - * + * * @return this element */ public Element attr(String attributeKey, boolean attributeValue) { @@ -287,7 +287,7 @@ private static void accumulateParents(Element el, Elements parents) { * Note that an element can have both mixed Nodes and Elements as children. This method inspects * a filtered list of children that are elements, and the index is based on that filtered list. * </p> - * + * * @param index the index number of the element to retrieve * @return the child element, if it exists, otherwise throws an {@code IndexOutOfBoundsException} * @see #childNode(int) @@ -410,7 +410,7 @@ public List<DataNode> dataNodes() { * </ul> * <p>See the query syntax documentation in {@link org.jsoup.select.Selector}.</p> * <p>Also known as {@code querySelectorAll()} in the Web DOM.</p> - * + * * @param cssQuery a {@link Selector} CSS-like query * @return an {@link Elements} list containing elements that match the query (empty if none match) * @see Selector selector query syntax @@ -506,10 +506,30 @@ public boolean is(Evaluator evaluator) { } while (el != null); return null; } - + + /** + <b>Beta:</b> find Elements that match the supplied XPath expression. + <p>(This functionality is currently in beta and + is subject to change. Feedback on the API is requested and welcomed!)</p> + <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an + alternate XPathFactory implementation:</p> + <ol> + <li>Add the implementation to your classpath. E.g. to use <a href="https://www.saxonica.com/products/products.xml">Saxon-HE</a>, add <a href="https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE">net.sf.saxon:Saxon-HE</a> to your build.</li> + <li>Set the system property <code>javax.xml.xpath.XPathFactory:jsoup</code> to the implementing classname. E.g.:<br> + <code>System.setProperty(W3CDom.XPathFactoryProperty, "net.sf.saxon.xpath.XPathFactoryImpl");</code> + </li> + </ol> + + @param xpath XML path expression + @return matching elements, or an empty list if none match. + */ + public Elements selectXpath(String xpath) { + return NodeUtils.selectXpath(xpath, this); + } + /** * Insert a node to the end of this Element's children. The incoming node will be re-parented. - * + * * @param child node to add. * @return this Element, for chaining * @see #prependChild(Node) @@ -552,13 +572,13 @@ public Element appendTo(Element parent) { /** * Add a node to the start of this element's children. - * + * * @param child node to add. * @return this element, so that you can add more child nodes or elements. */ public Element prependChild(Node child) { Validate.notNull(child); - + addChildren(0, child); return this; } @@ -615,10 +635,10 @@ public Element insertChildren(int index, Node... children) { addChildren(index, children); return this; } - + /** * Create a new element by tag name, and add it as the last child. - * + * * @param tagName the name of the tag (e.g. {@code div}). * @return the new element, to allow you to add content to it, e.g.: * {@code parent.appendElement("h1").attr("id", "header").text("Welcome");} @@ -628,10 +648,10 @@ public Element appendElement(String tagName) { appendChild(child); return child; } - + /** * Create a new element by tag name, and add it as the first child. - * + * * @param tagName the name of the tag (e.g. {@code div}). * @return the new element, to allow you to add content to it, e.g.: * {@code parent.prependElement("h1").attr("id", "header").text("Welcome");} @@ -641,10 +661,10 @@ public Element prependElement(String tagName) { prependChild(child); return child; } - + /** * Create and append a new TextNode to this element. - * + * * @param text the unencoded text to add * @return this element */ @@ -654,10 +674,10 @@ public Element appendText(String text) { appendChild(node); return this; } - + /** * Create and prepend a new TextNode to this element. - * + * * @param text the unencoded text to add * @return this element */ @@ -667,7 +687,7 @@ public Element prependText(String text) { prependChild(node); return this; } - + /** * Add inner HTML to this element. The supplied HTML will be parsed, and each node appended to the end of the children. * @param html HTML to add inside this element, after the existing HTML @@ -680,7 +700,7 @@ public Element append(String html) { addChildren(nodes.toArray(new Node[0])); return this; } - + /** * Add inner HTML into this element. The supplied HTML will be parsed, and each node prepended to the start of the element's children. * @param html HTML to add inside this element, before the existing HTML @@ -821,7 +841,7 @@ public Elements siblingElements() { } /** - * Gets the next sibling element of this element. E.g., if a {@code div} contains two {@code p}s, + * Gets the next sibling element of this element. E.g., if a {@code div} contains two {@code p}s, * the {@code nextElementSibling} of the first {@code p} is the second {@code p}. * <p> * This is similar to {@link #nextSibling()}, but specifically finds only Elements @@ -882,7 +902,7 @@ private Elements nextElementSiblings(boolean next) { /** * Gets the first Element sibling of this element. That may be this element. - * @return the first sibling that is an element (aka the parent's first element child) + * @return the first sibling that is an element (aka the parent's first element child) */ public Element firstElementSibling() { if (parent() != null) { @@ -891,7 +911,7 @@ public Element firstElementSibling() { } else return this; // orphan is its own first sibling } - + /** * Get the list index of this element in its element sibling list. I.e. if this is the first element * sibling, returns 0. @@ -904,7 +924,7 @@ public int elementSiblingIndex() { /** * Gets the last element sibling of this element. That may be this element. - * @return the last sibling that is an element (aka the parent's last element child) + * @return the last sibling that is an element (aka the parent's last element child) */ public Element lastElementSibling() { if (parent() != null) { @@ -948,7 +968,7 @@ public Elements getElementsByTag(String tagName) { */ public @Nullable Element getElementById(String id) { Validate.notEmpty(id); - + Elements elements = Collector.collect(new Evaluator.Id(id), this); if (elements.size() > 0) return elements.get(0); @@ -961,7 +981,7 @@ public Elements getElementsByTag(String tagName) { * <p> * Elements can have multiple classes (e.g. {@code <div class="header round first">}. This method * checks each class, so you can find the above with {@code el.getElementsByClass("header");}. - * + * * @param className the name of the class to search for. * @return elements with the supplied class name, empty if none * @see #hasClass(String) @@ -1001,7 +1021,7 @@ public Elements getElementsByAttributeStarting(String keyPrefix) { /** * Find elements that have an attribute with the specific value. Case insensitive. - * + * * @param key name of the attribute * @param value value of the attribute * @return elements that have this attribute with this value, empty if none @@ -1012,7 +1032,7 @@ public Elements getElementsByAttributeValue(String key, String value) { /** * Find elements that either do not have this attribute, or have it with a different value. Case insensitive. - * + * * @param key name of the attribute * @param value value of the attribute * @return elements that do not have a matching attribute @@ -1023,7 +1043,7 @@ public Elements getElementsByAttributeValueNot(String key, String value) { /** * Find elements that have attributes that start with the value prefix. Case insensitive. - * + * * @param key name of the attribute * @param valuePrefix start of attribute value * @return elements that have attributes that start with the value prefix @@ -1034,7 +1054,7 @@ public Elements getElementsByAttributeValueStarting(String key, String valuePref /** * Find elements that have attributes that end with the value suffix. Case insensitive. - * + * * @param key name of the attribute * @param valueSuffix end of the attribute value * @return elements that have attributes that end with the value suffix @@ -1045,7 +1065,7 @@ public Elements getElementsByAttributeValueEnding(String key, String valueSuffix /** * Find elements that have attributes whose value contains the match string. Case insensitive. - * + * * @param key name of the attribute * @param match substring of value to search for * @return elements that have attributes containing this text @@ -1053,7 +1073,7 @@ public Elements getElementsByAttributeValueEnding(String key, String valueSuffix public Elements getElementsByAttributeValueContaining(String key, String match) { return Collector.collect(new Evaluator.AttributeWithValueContaining(key, match), this); } - + /** * Find elements that have attributes whose values match the supplied regular expression. * @param key name of the attribute @@ -1062,9 +1082,9 @@ public Elements getElementsByAttributeValueContaining(String key, String match) */ public Elements getElementsByAttributeValueMatching(String key, Pattern pattern) { return Collector.collect(new Evaluator.AttributeWithValueMatching(key, pattern), this); - + } - + /** * Find elements that have attributes whose values match the supplied regular expression. * @param key name of the attribute @@ -1080,7 +1100,7 @@ public Elements getElementsByAttributeValueMatching(String key, String regex) { } return getElementsByAttributeValueMatching(key, pattern); } - + /** * Find elements whose sibling index is less than the supplied index. * @param index 0-based index @@ -1089,7 +1109,7 @@ public Elements getElementsByAttributeValueMatching(String key, String regex) { public Elements getElementsByIndexLessThan(int index) { return Collector.collect(new Evaluator.IndexLessThan(index), this); } - + /** * Find elements whose sibling index is greater than the supplied index. * @param index 0-based index @@ -1098,7 +1118,7 @@ public Elements getElementsByIndexLessThan(int index) { public Elements getElementsByIndexGreaterThan(int index) { return Collector.collect(new Evaluator.IndexGreaterThan(index), this); } - + /** * Find elements whose sibling index is equal to the supplied index. * @param index 0-based index @@ -1107,7 +1127,7 @@ public Elements getElementsByIndexGreaterThan(int index) { public Elements getElementsByIndexEquals(int index) { return Collector.collect(new Evaluator.IndexEquals(index), this); } - + /** * Find elements that contain the specified string. The search is case insensitive. The text may appear directly * in the element, or in any of its descendants. @@ -1118,7 +1138,7 @@ public Elements getElementsByIndexEquals(int index) { public Elements getElementsContainingText(String searchText) { return Collector.collect(new Evaluator.ContainsText(searchText), this); } - + /** * Find elements that directly contain the specified string. The search is case insensitive. The text must appear directly * in the element, not in any of its descendants. @@ -1129,7 +1149,7 @@ public Elements getElementsContainingText(String searchText) { public Elements getElementsContainingOwnText(String searchText) { return Collector.collect(new Evaluator.ContainsOwnText(searchText), this); } - + /** * Find elements whose text matches the supplied regular expression. * @param pattern regular expression to match text against @@ -1139,7 +1159,7 @@ public Elements getElementsContainingOwnText(String searchText) { public Elements getElementsMatchingText(Pattern pattern) { return Collector.collect(new Evaluator.Matches(pattern), this); } - + /** * Find elements whose text matches the supplied regular expression. * @param regex regular expression to match text against. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as (?i) and (?m) to control regex options. @@ -1155,7 +1175,7 @@ public Elements getElementsMatchingText(String regex) { } return getElementsMatchingText(pattern); } - + /** * Find elements whose own text matches the supplied regular expression. * @param pattern regular expression to match text against @@ -1165,7 +1185,7 @@ public Elements getElementsMatchingText(String regex) { public Elements getElementsMatchingOwnText(Pattern pattern) { return Collector.collect(new Evaluator.MatchesOwn(pattern), this); } - + /** * Find elements whose own text matches the supplied regular expression. * @param regex regular expression to match text against. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as (?i) and (?m) to control regex options. @@ -1181,10 +1201,10 @@ public Elements getElementsMatchingOwnText(String regex) { } return getElementsMatchingOwnText(pattern); } - + /** * Find all elements under this element (including self, and children of children). - * + * * @return all elements */ public Elements getAllElements() { @@ -1389,7 +1409,7 @@ public String data() { } } return StringUtil.releaseBuilder(sb); - } + } /** * Gets the literal value of this element's "class" attribute, which may include multiple class names, space @@ -1528,7 +1548,7 @@ public Element toggleClass(String className) { return this; } - + /** * Get the value of a form element (input, textarea, etc). * @return the value of the form element, or empty string if not set. @@ -1539,7 +1559,7 @@ public String val() { else return attr("value"); } - + /** * Set the value of a form element (input, textarea, etc). * @param value value to set @@ -1589,7 +1609,7 @@ void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) thr /** * Retrieves the element's inner HTML. E.g. on a {@code <div>} with one empty {@code <p>}, would return * {@code <p></p>}. (Whereas {@link #outerHtml()} would return {@code <div><p></p></div>}.) - * + * * @return String of HTML. * @see #outerHtml() */ @@ -1608,7 +1628,7 @@ public <T extends Appendable> T html(T appendable) { return appendable; } - + /** * Set this element's inner HTML. Clears the existing HTML first. * @param html HTML to parse and set into this element diff --git a/src/main/java/org/jsoup/nodes/NodeUtils.java b/src/main/java/org/jsoup/nodes/NodeUtils.java index 2a3983903b..107f4a8be4 100644 --- a/src/main/java/org/jsoup/nodes/NodeUtils.java +++ b/src/main/java/org/jsoup/nodes/NodeUtils.java @@ -1,7 +1,11 @@ package org.jsoup.nodes; +import org.jsoup.helper.Validate; +import org.jsoup.helper.W3CDom; import org.jsoup.parser.HtmlTreeBuilder; import org.jsoup.parser.Parser; +import org.jsoup.select.Elements; +import org.w3c.dom.NodeList; /** * Internal helpers for Nodes, to keep the actual node APIs relatively clean. A jsoup internal class, so don't use it as @@ -24,4 +28,20 @@ static Parser parser(Node node) { Document doc = node.ownerDocument(); return doc != null && doc.parser() != null ? doc.parser() : new Parser(new HtmlTreeBuilder()); } + + /** + This impl works by compiling the input xpath expression, and then evaluating it against a W3C Document converted + from the original jsoup element. The original jsoup elements are then fetched from the w3c doc user data (where we + stashed them during conversion). This process could potentially be optimized by transpiling the compiled xpath + expression to a jsoup Evaluator when there's 1:1 support, thus saving the W3C document conversion stage. + */ + static Elements selectXpath(String xpath, Element el) { + Validate.notEmpty(xpath); + Validate.notNull(el); + + W3CDom w3c = new W3CDom(); + org.w3c.dom.Document wDoc = w3c.fromJsoup(el); + NodeList nodeList = w3c.selectXpath(xpath, wDoc); + return w3c.sourceElements(nodeList); + } } diff --git a/src/test/java/org/jsoup/TextUtil.java b/src/test/java/org/jsoup/TextUtil.java index ca49bef8da..54bf5b7a6e 100644 --- a/src/test/java/org/jsoup/TextUtil.java +++ b/src/test/java/org/jsoup/TextUtil.java @@ -8,12 +8,23 @@ @author Jonathan Hedley, jonathan@hedley.net */ public class TextUtil { static Pattern stripper = Pattern.compile("\\r?\\n\\s*"); + static Pattern stripLines = Pattern.compile("\\r?\\n?"); + static Pattern spaceCollapse = Pattern.compile("\\s{2,}"); + static Pattern tagSpaceCollapse = Pattern.compile(">\\s+<"); static Pattern stripCRs = Pattern.compile("\\r*"); public static String stripNewlines(String text) { return stripper.matcher(text).replaceAll(""); } + public static String normalizeSpaces(String text) { + text = stripLines.matcher(text).replaceAll(""); + text = stripper.matcher(text).replaceAll(""); + text = spaceCollapse.matcher(text).replaceAll(" "); + text = tagSpaceCollapse.matcher(text).replaceAll("><"); + return text; + } + public static String stripCRs(String text) { return stripCRs.matcher(text).replaceAll(""); } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index c2253bf5e7..1c172a4dd0 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -4,6 +4,7 @@ import org.jsoup.TextUtil; import org.jsoup.integration.ParseTest; import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; @@ -23,6 +24,7 @@ import java.io.IOException; import java.io.StringReader; import java.nio.charset.StandardCharsets; +import java.util.Locale; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -259,27 +261,28 @@ private NodeList xpath(Document w3cDoc, String query) throws XPathExpressionExce @Test public void testRoundTripDoctype() { // TODO - not super happy with this output - but plain DOM doesn't let it out, and don't want to rebuild the writer + // because we have Saxon on the test classpath, the transformer will change to that, and so case may change (e.g. Java base in META, Saxon is meta for HTML) String base = "<!DOCTYPE html><p>One</p>"; - assertEquals("<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p></body></html>", + assertEqualsIgnoreCase("<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p></body></html>", output(base, true)); - assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head/><body><p>One</p></body></html>", output(base, false)); + assertEqualsIgnoreCase("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head/><body><p>One</p></body></html>", output(base, false)); String publicDoc = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; - assertEquals("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(publicDoc, true)); + assertEqualsIgnoreCase("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(publicDoc, true)); // different impls will have different XML formatting. OpenJDK 13 default gives this: <body /> but others have <body/>, so just check start assertTrue(output(publicDoc, false).startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC")); String systemDoc = "<!DOCTYPE html SYSTEM \"exampledtdfile.dtd\">"; - assertEquals("<!DOCTYPE html SYSTEM \"exampledtdfile.dtd\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(systemDoc, true)); - assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html SYSTEM \"exampledtdfile.dtd\"><html><head/><body/></html>", output(systemDoc, false)); + assertEqualsIgnoreCase("<!DOCTYPE html SYSTEM \"exampledtdfile.dtd\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(systemDoc, true)); + assertEqualsIgnoreCase("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html SYSTEM \"exampledtdfile.dtd\"><html><head/><body/></html>", output(systemDoc, false)); String legacyDoc = "<!DOCTYPE html SYSTEM \"about:legacy-compat\">"; - assertEquals("<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(legacyDoc, true)); - assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head/><body/></html>", output(legacyDoc, false)); + assertEqualsIgnoreCase("<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", output(legacyDoc, true)); + assertEqualsIgnoreCase("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head/><body/></html>", output(legacyDoc, false)); String noDoctype = "<p>One</p>"; - assertEquals("<html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p></body></html>", output(noDoctype, true)); - assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><head/><body><p>One</p></body></html>", output(noDoctype, false)); + assertEqualsIgnoreCase("<html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p></body></html>", output(noDoctype, true)); + assertEqualsIgnoreCase("<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><head/><body><p>One</p></body></html>", output(noDoctype, false)); } private String output(String in, boolean modeHtml) { @@ -287,7 +290,30 @@ private String output(String in, boolean modeHtml) { Document w3c = W3CDom.convert(jdoc); Map<String, String> properties = modeHtml ? W3CDom.OutputHtml() : W3CDom.OutputXml(); - return TextUtil.stripNewlines(W3CDom.asString(w3c, properties)); + return TextUtil.normalizeSpaces(W3CDom.asString(w3c, properties)); + } + + private void assertEqualsIgnoreCase(String want, String have) { + assertEquals(want.toLowerCase(Locale.ROOT), have.toLowerCase(Locale.ROOT)); + } + + @Test public void convertsElementsAndMaintainsSource() { + org.jsoup.nodes.Document jdoc = Jsoup.parse("<body><div><p>One</div><div><p>Two"); + W3CDom w3CDom = new W3CDom(); + Element jDiv = jdoc.selectFirst("div"); + assertNotNull(jDiv); + Document doc = w3CDom.fromJsoup(jDiv); + Node div = doc.getFirstChild(); + + assertEquals("div", div.getLocalName()); + assertEquals(jDiv, div.getUserData(W3CDom.SourceProperty)); + + Node textNode = div.getFirstChild().getFirstChild(); + assertEquals("One", textNode.getTextContent()); + assertEquals(Node.TEXT_NODE, textNode.getNodeType()); + + org.jsoup.nodes.TextNode jText = (TextNode) jDiv.childNode(0).childNode(0); + assertEquals(jText, textNode.getUserData(W3CDom.SourceProperty)); } } diff --git a/src/test/java/org/jsoup/select/XpathTest.java b/src/test/java/org/jsoup/select/XpathTest.java new file mode 100644 index 0000000000..e207be56b4 --- /dev/null +++ b/src/test/java/org/jsoup/select/XpathTest.java @@ -0,0 +1,177 @@ +package org.jsoup.select; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.parser.Parser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFactoryConfigurationException; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; + +import java.util.stream.Stream; + +import static org.jsoup.helper.W3CDom.XPathFactoryProperty; +import static org.junit.jupiter.api.Assertions.*; + +public class XpathTest { + + @Test + public void supportsXpath() { + String html = "<body><div><p>One</div><div><p>Two</div><div>Three</div>"; + Document doc = Jsoup.parse(html); + + Elements els = doc.selectXpath("//div/p"); + assertEquals(2, els.size()); + assertEquals("One", els.get(0).text()); + assertEquals("Two", els.get(1).text()); + } + + @Test public void supportsXpathFromElement() { + String html = "<body><div><p>One</div><div><p>Two</div><div>Three</div>"; + Document doc = Jsoup.parse(html); + + Element div = doc.selectFirst("div"); + assertNotNull(div); + + Elements els = div.selectXpath("/div/p"); + assertEquals(1, els.size()); + assertEquals("One", els.get(0).text()); + assertEquals("p", els.get(0).tagName()); + + assertEquals(0, div.selectXpath("//body").size()); + assertEquals(1, doc.selectXpath("//body").size()); + } + + @Test public void emptyElementsIfNoResults() { + Document doc = Jsoup.parse("<p>One<p>Two"); + assertEquals(0, doc.selectXpath("//div").size()); + } + + @Test + public void throwsSelectException() { + Document doc = Jsoup.parse("<p>One<p>Two"); + boolean threw = false; + try { + doc.selectXpath("//???"); + } catch (Selector.SelectorParseException e) { + threw = true; + // checks exception message within jsoup's control, rest may be JDK impl specific + // was - Could not evaluate XPath query [//???]: javax.xml.transform.TransformerException: A location step was expected following the '/' or '//' token. + assertTrue(e.getMessage().startsWith("Could not evaluate XPath query [//???]:")); + } + assertTrue(threw); + } + + @Test + public void supportsNamespaces() { + String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>";; + Document doc = Jsoup.parse(xhtml, Parser.xmlParser()); + Elements elements = doc.selectXpath("//*[local-name()='body']"); + assertEquals(1, elements.size()); + assertEquals("One", elements.first().id()); + } + + @Test + public void canDitchNamespaces() { + String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>";; + Document doc = Jsoup.parse(xhtml, Parser.xmlParser()); + doc.select("[xmlns]").removeAttr("xmlns"); + Elements elements = doc.selectXpath("//*[local-name()='body']"); + assertEquals(1, elements.size()); + + elements = doc.selectXpath("//body"); + assertEquals(1, elements.size()); + assertEquals("One", elements.first().id()); + } + + @ParameterizedTest + @MethodSource("provideEvaluators") + void cssAndXpathEquivalents(Document doc, String css, String xpath) { + Elements fromCss = doc.select(css); + Elements fromXpath = doc.selectXpath(xpath); + + assertTrue(fromCss.size() >= 1); + assertTrue(fromXpath.size() >= 1); + // tests same size, order, and contents + assertEquals(fromCss, fromXpath); + } + + private static Stream<Arguments> provideEvaluators() { + String html = "<div id=1><div id=2><p class=foo>Hello</p></div></div><DIV id=3>"; + Document doc = Jsoup.parse(html); + + return Stream.of( + Arguments.of(doc, "DIV", "//div"), + Arguments.of(doc, "div > p.foo", "//div/p[@class]"), + Arguments.of(doc, "div + div", "//div/following-sibling::div[1]"), + Arguments.of(doc, "p:containsOwn(Hello)", "//p[contains(text(),\"Hello\")]") + ); + } + + @Test + public void canSupplyAlternateFactoryImpl() { + // previously we had a test to load Saxon and do an XPath 2.0 query. But we know Saxon works and so that's + // redundant - really just need to test that an alternate XPath factory can be used + + System.setProperty(XPathFactoryProperty, AlternateXpathFactory.class.getName()); + + String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>"; + boolean threw = false; + try { + Document doc = Jsoup.parse(xhtml, Parser.xmlParser()); + Elements elements = doc.selectXpath("//*:body"); + + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("Sorry, no can do!")); + threw = true; + } + assertTrue(threw); + System.clearProperty(XPathFactoryProperty); + + + } + + // minimal, no-op implementation class to verify users can load a factory to support XPath 2.0 etc + public static class AlternateXpathFactory extends XPathFactory { + public AlternateXpathFactory() { + super(); + } + + @Override + public boolean isObjectModelSupported(String objectModel) { + return true; + } + + @Override + public void setFeature(String name, boolean value) throws XPathFactoryConfigurationException { + + } + + @Override + public boolean getFeature(String name) throws XPathFactoryConfigurationException { + return true; + } + + @Override + public void setXPathVariableResolver(XPathVariableResolver resolver) { + + } + + @Override + public void setXPathFunctionResolver(XPathFunctionResolver resolver) { + + } + + @Override + public XPath newXPath() { + throw new IllegalArgumentException("Sorry, no can do!"); + } + } +} From 14082c181b17343ce32b8f1be3240b5cc5564249 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 13 Sep 2021 14:57:57 +1000 Subject: [PATCH 622/774] Version updates for japicmp --- pom.xml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 249ed7a892..09516e4747 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.3-SNAPSHOT</version> + <version>1.14.3-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -199,7 +199,7 @@ <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.13.1</version> + <version>1.14.2</version> <type>jar</type> </dependency> </oldVersion> @@ -208,14 +208,6 @@ <onlyModified>false</onlyModified> <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> - <overrideCompatibilityChangeParameters> - <!-- 1.14.1 interface adds for cookies. rarely implemented outside of jsoup, and can't provide default impl in 7, so flag in changelog --> - <overrideCompatibilityChangeParameter> - <compatibilityChange>METHOD_ADDED_TO_INTERFACE</compatibilityChange> - <binaryCompatible>true</binaryCompatible> - <sourceCompatible>true</sourceCompatible> - </overrideCompatibilityChangeParameter> - </overrideCompatibilityChangeParameters> </parameter> </configuration> <executions> From 2062e9809c9a00327022efba65feae7f17e517d2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 13 Sep 2021 15:03:21 +1000 Subject: [PATCH 623/774] Bring jetty versions in sync --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 09516e4747..b51bb8844d 100644 --- a/pom.xml +++ b/pom.xml @@ -325,7 +325,7 @@ <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.42.v20210604</version> + <version>9.4.43.v20210629</version> <scope>test</scope> </dependency> From b0c206f0dad656803c9b8a6224dbd68930d1ed77 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 13 Sep 2021 15:15:24 +1000 Subject: [PATCH 624/774] Re-enabled ConstrainedInputStream tests Using local Jetty server, in IT tests --- .../java/org/jsoup/integration/ConnectIT.java | 73 ++++++++++++++++- .../ConstrainableInputStreamTest.java | 78 ------------------- 2 files changed, 70 insertions(+), 81 deletions(-) delete mode 100644 src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java index 4e74c35b70..84315b4a82 100644 --- a/src/test/java/org/jsoup/integration/ConnectIT.java +++ b/src/test/java/org/jsoup/integration/ConnectIT.java @@ -2,22 +2,26 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; +import org.jsoup.integration.servlets.FileServlet; import org.jsoup.integration.servlets.SlowRider; +import org.jsoup.internal.ConstrainableInputStream; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; import java.io.IOException; import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * Failsafe integration tests for Connect methods. These take a bit longer to run, so included as Integ, not Unit, tests. */ public class ConnectIT { - // Slow Rider tests. Ignored by default so tests don't take aaages + // Slow Rider tests. @Test public void canInterruptBodyStringRead() throws InterruptedException { // todo - implement in interruptable channels, so it's immediate @@ -108,4 +112,67 @@ public void infiniteReadSupported() throws IOException { Element h1 = doc.selectFirst("h1"); assertEquals("outatime", h1.text()); } + + @Test + public void remainingAfterFirstRead() throws IOException { + int bufferSize = 5 * 1024; + int capSize = 100 * 1024; + + String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K + ConstrainableInputStream stream; + try (BufferedInputStream inputStream = Jsoup.connect(url).maxBodySize(capSize) + .execute().bodyStream()) { + + assertTrue(inputStream instanceof ConstrainableInputStream); + stream = (ConstrainableInputStream) inputStream; + + // simulates parse which does a limited read first + stream.mark(bufferSize); + ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); + + byte[] array = firstBytes.array(); + String firstText = new String(array, StandardCharsets.UTF_8); + assertTrue(firstText.startsWith("<html><head><title>Large")); + assertEquals(bufferSize, array.length); + + boolean fullyRead = stream.read() == -1; + assertFalse(fullyRead); + + // reset and read again + stream.reset(); + ByteBuffer fullRead = stream.readToByteBuffer(0); + byte[] fullArray = fullRead.array(); + assertEquals(capSize, fullArray.length); + String fullText = new String(fullArray, StandardCharsets.UTF_8); + assertTrue(fullText.startsWith(firstText)); + } + } + + @Test + public void noLimitAfterFirstRead() throws IOException { + int bufferSize = 5 * 1024; + + String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K + ConstrainableInputStream stream; + try (BufferedInputStream inputStream = Jsoup.connect(url).execute().bodyStream()) { + assertTrue(inputStream instanceof ConstrainableInputStream); + stream = (ConstrainableInputStream) inputStream; + + // simulates parse which does a limited read first + stream.mark(bufferSize); + ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); + byte[] array = firstBytes.array(); + String firstText = new String(array, StandardCharsets.UTF_8); + assertTrue(firstText.startsWith("<html><head><title>Large")); + assertEquals(bufferSize, array.length); + + // reset and read fully + stream.reset(); + ByteBuffer fullRead = stream.readToByteBuffer(0); + byte[] fullArray = fullRead.array(); + assertEquals(280735, fullArray.length); + String fullText = new String(fullArray, StandardCharsets.UTF_8); + assertTrue(fullText.startsWith(firstText)); + } + } } diff --git a/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java b/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java deleted file mode 100644 index 6a7ad96103..0000000000 --- a/src/test/java/org/jsoup/internal/ConstrainableInputStreamTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.jsoup.internal; - -import org.jsoup.Jsoup; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.*; - -@Disabled -public class ConstrainableInputStreamTest { - // todo - move these all to local jetty, don't ignore - - @Test - public void remainingAfterFirstRead() throws IOException { - int bufferSize = 5 * 1024; - int capSize = 100 * 1024; - - String url = "http://direct.infohound.net/tools/large.html"; // 280 K - BufferedInputStream inputStream = Jsoup.connect(url).maxBodySize(capSize) - .execute().bodyStream(); - - assertTrue(inputStream instanceof ConstrainableInputStream); - ConstrainableInputStream stream = (ConstrainableInputStream) inputStream; - - // simulates parse which does a limited read first - stream.mark(bufferSize); - ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); - - byte[] array = firstBytes.array(); - String firstText = new String(array, StandardCharsets.UTF_8); - assertTrue(firstText.startsWith("<html><head><title>Large")); - assertEquals(bufferSize, array.length); - - boolean fullyRead = stream.read() == -1; - assertFalse(fullyRead); - - // reset and read again - stream.reset(); - ByteBuffer fullRead = stream.readToByteBuffer(0); - byte[] fullArray = fullRead.array(); - assertEquals(capSize, fullArray.length); - String fullText = new String(fullArray, StandardCharsets.UTF_8); - assertTrue(fullText.startsWith(firstText)); - } - - @Test - public void noLimitAfterFirstRead() throws IOException { - int bufferSize = 5 * 1024; - - String url = "http://direct.infohound.net/tools/large.html"; // 280 K - BufferedInputStream inputStream = Jsoup.connect(url).execute().bodyStream(); - - assertTrue(inputStream instanceof ConstrainableInputStream); - ConstrainableInputStream stream = (ConstrainableInputStream) inputStream; - - // simulates parse which does a limited read first - stream.mark(bufferSize); - ByteBuffer firstBytes = stream.readToByteBuffer(bufferSize); - byte[] array = firstBytes.array(); - String firstText = new String(array, StandardCharsets.UTF_8); - assertTrue(firstText.startsWith("<html><head><title>Large")); - assertEquals(bufferSize, array.length); - - // reset and read fully - stream.reset(); - ByteBuffer fullRead = stream.readToByteBuffer(0); - byte[] fullArray = fullRead.array(); - assertEquals(280735, fullArray.length); - String fullText = new String(fullArray, StandardCharsets.UTF_8); - assertTrue(fullText.startsWith(firstText)); - - } -} From 65ca248e3612e3cd24eed20a9aab2e77c4e85228 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 13 Sep 2021 17:47:57 +1000 Subject: [PATCH 625/774] Fix javadoc warning --- src/main/java/org/jsoup/helper/W3CDom.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 11a595df19..e2c2bd1a6c 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -50,8 +50,8 @@ public class W3CDom { public static final String SourceProperty = "jsoupSource"; /** - To get support for XPath versions > 1, set this property to the classname of an alternate XPathFactory - implementation (e.g. <code>net.sf.saxon.xpath.XPathFactoryImpl</code>). + To get support for XPath versions &gt; 1, set this property to the classname of an alternate XPathFactory + implementation (e.g. {@code net.sf.saxon.xpath.XPathFactoryImpl}). */ public static final String XPathFactoryProperty = "javax.xml.xpath.XPathFactory:jsoup"; From c5bab9175c628558eb0abbdb205e0b9903bec9e1 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 13 Sep 2021 21:36:30 +1000 Subject: [PATCH 626/774] Attribute::equals should not be order sensitive Fixes #1492 --- CHANGES | 3 ++ src/main/java/org/jsoup/nodes/Attributes.java | 19 ++++++++--- .../java/org/jsoup/nodes/AttributesTest.java | 34 +++++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 9d1c77ba77..2be4789018 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ jsoup changelog dependency for nullability assertions). <https://github.com/jhy/jsoup/issues/1616> + * Bugfix: the Attributes::equals() method was sensitive to the order of its contents, but it should not be. + <https://github.com/jhy/jsoup/issues/1492> + * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods. <https://github.com/jhy/jsoup/issues/1628> diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 6b3e8cd0b6..bf96da6152 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -371,7 +371,8 @@ public String toString() { } /** - * Checks if these attributes are equal to another set of attributes, by comparing the two sets + * Checks if these attributes are equal to another set of attributes, by comparing the two sets. Note that the order + * of the attributes does not impact this equality (as per the Map interface equals()). * @param o attributes to compare with * @return if both sets of attributes have the same content */ @@ -381,10 +382,20 @@ public boolean equals(@Nullable Object o) { if (o == null || getClass() != o.getClass()) return false; Attributes that = (Attributes) o; - if (size != that.size) return false; - if (!Arrays.equals(keys, that.keys)) return false; - return Arrays.equals(vals, that.vals); + for (int i = 0; i < size; i++) { + String key = keys[i]; + String val = vals[i]; + if (!that.hasKey(key)) + return false; + String thatVal = that.vals[that.indexOfKey(key)]; + if (val == null) { + if (thatVal != null) + return false; + } else if (!vals[i].equals(that.get(key))) + return false; + } + return true; } /** diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 51bde98427..16a60b835d 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -267,4 +267,38 @@ public void testBoolean() { assertEquals("checked", attribute.html()); assertEquals(" checked", attributes.html()); } + + @Test public void equalsIsOrderInsensitive() { + Attributes one = new Attributes() + .add("Key1", "Val1") + .add("Key2", "Val2") + .add("Key3", null); + + Attributes two = new Attributes() + .add("Key1", "Val1") + .add("Key2", "Val2") + .add("Key3", null); + + Attributes three = new Attributes() + .add("Key2", "Val2") + .add("Key3", null) + .add("Key1", "Val1"); + + Attributes four = new Attributes() + .add("Key1", "Val1") + .add("Key2", "Val2") + .add("Key3", null) + .add("Key4", "Val4"); + + assertEquals(one, one.clone()); + assertEquals(one, two); + assertEquals(two, two); + assertEquals(one, three); + assertEquals(two, three); + assertEquals(three, three); + assertEquals(three, three.clone()); + assertEquals(four, four); + assertEquals(four, four.clone()); + assertNotEquals(one, four); + } } From ecf09c27a69769d8d4957bb45da7c7bb1397edcf Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 13 Sep 2021 22:01:56 +1000 Subject: [PATCH 627/774] Fewer key scans in Attributes::equals Improves fix for #1492 --- src/main/java/org/jsoup/nodes/Attributes.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index bf96da6152..bf59ec08fa 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -385,14 +385,15 @@ public boolean equals(@Nullable Object o) { if (size != that.size) return false; for (int i = 0; i < size; i++) { String key = keys[i]; - String val = vals[i]; - if (!that.hasKey(key)) + int thatI = that.indexOfKey(key); + if (thatI == NotFound) return false; - String thatVal = that.vals[that.indexOfKey(key)]; + String val = vals[i]; + String thatVal = that.vals[thatI]; if (val == null) { if (thatVal != null) return false; - } else if (!vals[i].equals(that.get(key))) + } else if (!val.equals(thatVal)) return false; } return true; From fb285f56d5e357673261ee17dcf5e4bfd57ef54c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 12:22:37 +1000 Subject: [PATCH 628/774] Bump junit-jupiter from 5.7.2 to 5.8.0 (#1631) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.7.2 to 5.8.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.7.2...r5.8.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b51bb8844d..ae8811c976 100644 --- a/pom.xml +++ b/pom.xml @@ -301,7 +301,7 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> - <version>5.7.2</version> + <version>5.8.0</version> <scope>test</scope> </dependency> From ec4bedf219d6bff7796d9f37c0455458f507bdd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 12:24:54 +1000 Subject: [PATCH 629/774] Bump maven-javadoc-plugin from 3.3.0 to 3.3.1 (#1632) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.0...maven-javadoc-plugin-3.3.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae8811c976..99b0c381a8 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.3.0</version> + <version>3.3.1</version> <configuration> <doclint>none</doclint> <source>7</source> From 2bf377f72faa3b85e1a6ef66a21fca9c04001abf Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 14 Sep 2021 16:40:47 +1000 Subject: [PATCH 630/774] Added support for XPath selection for specific node types --- src/main/java/org/jsoup/helper/W3CDom.java | 16 ++++++---- src/main/java/org/jsoup/nodes/Attributes.java | 2 +- src/main/java/org/jsoup/nodes/Element.java | 20 ++++++++++-- src/main/java/org/jsoup/nodes/NodeUtils.java | 7 +++-- src/test/java/org/jsoup/select/XpathTest.java | 31 +++++++++++++++++++ 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index e2c2bd1a6c..79ada4494a 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -3,7 +3,6 @@ import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; -import org.jsoup.select.Elements; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import org.jsoup.select.Selector; @@ -33,7 +32,9 @@ import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactoryConfigurationException; import java.io.StringWriter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Stack; @@ -244,16 +245,19 @@ public NodeList selectXpath(String xpath, Document doc) { return nodeList; } - public Elements sourceElements(NodeList nodeList) { - Elements els = new Elements(); + public <T extends org.jsoup.nodes.Node> List<T> sourceNodes(NodeList nodeList, Class<T> nodeType) { + Validate.notNull(nodeList); + Validate.notNull(nodeType); + List<T> nodes = new ArrayList<>(nodeList.getLength()); + for (int i = 0; i < nodeList.getLength(); i++) { org.w3c.dom.Node node = nodeList.item(i); Object source = node.getUserData(W3CDom.SourceProperty); - if (source instanceof org.jsoup.nodes.Element) - els.add((org.jsoup.nodes.Element) source); + if (nodeType.isInstance(source)) + nodes.add(nodeType.cast(source)); } - return els; + return nodes; } /** diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index bf59ec08fa..fc442ef822 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -25,7 +25,7 @@ * Attributes are treated as a map: there can be only one value associated with an attribute key/name. * </p> * <p> - * Attribute name and value comparisons are generally <b>case sensitive</b>. By default for HTML, attribute names are + * Attribute name and value comparisons are generally <b>case sensitive</b>. By default for HTML, attribute names are * normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by * name. * </p> diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index c1e08190dc..74381716bc 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -520,11 +520,27 @@ public boolean is(Evaluator evaluator) { </li> </ol> - @param xpath XML path expression + @param xpath XPath expression @return matching elements, or an empty list if none match. */ public Elements selectXpath(String xpath) { - return NodeUtils.selectXpath(xpath, this); + return new Elements(NodeUtils.selectXpath(xpath, this, Element.class)); + } + + /** + <b>Beta:</b> find Nodes that match the supplied XPath expression. + <p>For example, to select TextNodes under {@code p} elements: </p> + <pre>List&lt;TextNode&gt; textNodes = doc.selectXpath("//body//p//text()", TextNode.class);</pre> + <p>Note that in the jsoup DOM, Attribute objects are not Nodes. To directly select attribute values, do something + like:</p> + <pre>List&lt;String&gt; hrefs = doc.selectXpath("//a").eachAttr("href");</pre> + @param xpath XPath expression + @param nodeType the jsoup node type to return + @see #selectXpath(String) + @return a list of matching nodes + */ + public <T extends Node> List<T> selectXpath(String xpath, Class<T> nodeType) { + return NodeUtils.selectXpath(xpath, this, nodeType); } /** diff --git a/src/main/java/org/jsoup/nodes/NodeUtils.java b/src/main/java/org/jsoup/nodes/NodeUtils.java index 107f4a8be4..ea6f08159f 100644 --- a/src/main/java/org/jsoup/nodes/NodeUtils.java +++ b/src/main/java/org/jsoup/nodes/NodeUtils.java @@ -7,6 +7,8 @@ import org.jsoup.select.Elements; import org.w3c.dom.NodeList; +import java.util.List; + /** * Internal helpers for Nodes, to keep the actual node APIs relatively clean. A jsoup internal class, so don't use it as * there is no contract API). @@ -35,13 +37,14 @@ from the original jsoup element. The original jsoup elements are then fetched fr stashed them during conversion). This process could potentially be optimized by transpiling the compiled xpath expression to a jsoup Evaluator when there's 1:1 support, thus saving the W3C document conversion stage. */ - static Elements selectXpath(String xpath, Element el) { + static <T extends Node> List<T> selectXpath(String xpath, Element el, Class<T> nodeType) { Validate.notEmpty(xpath); Validate.notNull(el); + Validate.notNull(nodeType); W3CDom w3c = new W3CDom(); org.w3c.dom.Document wDoc = w3c.fromJsoup(el); NodeList nodeList = w3c.selectXpath(xpath, wDoc); - return w3c.sourceElements(nodeList); + return w3c.sourceNodes(nodeList, nodeType); } } diff --git a/src/test/java/org/jsoup/select/XpathTest.java b/src/test/java/org/jsoup/select/XpathTest.java index e207be56b4..e46f17978c 100644 --- a/src/test/java/org/jsoup/select/XpathTest.java +++ b/src/test/java/org/jsoup/select/XpathTest.java @@ -1,8 +1,11 @@ package org.jsoup.select; import org.jsoup.Jsoup; +import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -15,6 +18,7 @@ import javax.xml.xpath.XPathFunctionResolver; import javax.xml.xpath.XPathVariableResolver; +import java.util.List; import java.util.stream.Stream; import static org.jsoup.helper.W3CDom.XPathFactoryProperty; @@ -115,6 +119,33 @@ private static Stream<Arguments> provideEvaluators() { ); } + @Test void canSelectTextNodes() { + String html = "<div><p>One<p><a>Two</a><p>Three and some more"; + Document doc = Jsoup.parse(html); + + // as text nodes: + List<TextNode> text = doc.selectXpath("//body//p//text()", TextNode.class); + assertEquals(3, text.size()); + assertEquals("One", text.get(0).text()); + assertEquals("Two", text.get(1).text()); + assertEquals("Three and some more", text.get(2).text()); + + // as just nodes: + List<Node> nodes = doc.selectXpath("//body//p//text()", Node.class); + assertEquals(3, nodes.size()); + assertEquals("One", nodes.get(0).outerHtml()); + assertEquals("Two", nodes.get(1).outerHtml()); + assertEquals("Three and some more", nodes.get(2).outerHtml()); + } + + @Test void selectByAttribute() { + Document doc = Jsoup.parse("<p><a href='/foo'>Foo</a><a href='/bar'>Bar</a><a>None</a>"); + List<String> hrefs = doc.selectXpath("//a[@href]").eachAttr("href"); + assertEquals(2, hrefs.size()); + assertEquals("/foo", hrefs.get(0)); + assertEquals("/bar", hrefs.get(1)); + } + @Test public void canSupplyAlternateFactoryImpl() { // previously we had a test to load Saxon and do an XPath 2.0 query. But we know Saxon works and so that's From d80c0a99da0ff53a15d85381dd92a2a8f9742d66 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Sep 2021 15:24:14 +1000 Subject: [PATCH 631/774] Substantially reduced GC for :has() selectors The getAllElements created a lot of garbage objects during query evaluation. Removing that gives a meaningful speedup. --- CHANGES | 2 ++ src/main/java/org/jsoup/select/Collector.java | 21 ++++++++++++------- .../java/org/jsoup/select/QueryParser.java | 2 +- .../org/jsoup/select/StructuralEvaluator.java | 15 ++++++++++--- .../java/org/jsoup/select/SelectorTest.java | 2 +- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 2be4789018..4e8c82de8d 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ jsoup changelog * Improvement: tracked parse errors now have more details, including the erroneous token, to help clarify the errors. + * Improvement: speed and memory optimizations for the :has(subquery) selector. + * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. diff --git a/src/main/java/org/jsoup/select/Collector.java b/src/main/java/org/jsoup/select/Collector.java index fb9177aa44..ea1d5cf442 100644 --- a/src/main/java/org/jsoup/select/Collector.java +++ b/src/main/java/org/jsoup/select/Collector.java @@ -61,26 +61,31 @@ public void tail(Node node, int depth) { @return the first match; {@code null} if none */ public static @Nullable Element findFirst(Evaluator eval, Element root) { - FirstFinder finder = new FirstFinder(root, eval); - NodeTraversor.filter(finder, root); - return finder.match; + FirstFinder finder = new FirstFinder(eval); + return finder.find(root, root); } - private static class FirstFinder implements NodeFilter { - private final Element root; + static class FirstFinder implements NodeFilter { + private @Nullable Element evalRoot = null; private @Nullable Element match = null; private final Evaluator eval; - FirstFinder(Element root, Evaluator eval) { - this.root = root; + FirstFinder(Evaluator eval) { this.eval = eval; } + @Nullable Element find(Element root, Element start) { + evalRoot = root; + match = null; + NodeTraversor.filter(this, start); + return match; + } + @Override public FilterResult head(Node node, int depth) { if (node instanceof Element) { Element el = (Element) node; - if (eval.matches(root, el)) { + if (eval.matches(evalRoot, el)) { match = el; return STOP; } diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index fb2c0797dd..bed123b494 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -352,7 +352,7 @@ private int consumeIndex() { private void has() { tq.consume(":has"); String subQuery = tq.chompBalanced('(', ')'); - Validate.notEmpty(subQuery, ":has(el) subselect must not be empty"); + Validate.notEmpty(subQuery, ":has(selector) subselect must not be empty"); evals.add(new StructuralEvaluator.Has(parse(subQuery))); } diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index d35df3c1e0..7d13ab6610 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -1,6 +1,7 @@ package org.jsoup.select; import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; /** * Base structural evaluator. @@ -15,14 +16,22 @@ public boolean matches(Element root, Element element) { } static class Has extends StructuralEvaluator { + final Collector.FirstFinder finder; + public Has(Evaluator evaluator) { this.evaluator = evaluator; + finder = new Collector.FirstFinder(evaluator); } public boolean matches(Element root, Element element) { - for (Element e : element.getAllElements()) { - if (e != element && evaluator.matches(element, e)) - return true; + // for :has, we only want to match children (or below), not the input element. And we want to minimize GCs + for (int i = 0; i < element.childNodeSize(); i++) { + Node node = element.childNode(i); + if (node instanceof Element) { + Element match = finder.find(element, (Element) node); + if (match != null) + return true; + } } return false; } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index def16ed234..eba28ee8c2 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -546,7 +546,7 @@ public void testByAttributeStarting(Locale locale) { assertEquals("2", divs3.get(2).id()); Elements els1 = doc.body().select(":has(p)"); - assertEquals(3, els1.size()); // body, div, dib + assertEquals(3, els1.size()); // body, div, div assertEquals("body", els1.first().tagName()); assertEquals("0", els1.get(1).id()); assertEquals("2", els1.get(2).id()); From bc4720a2c13a321a40a9a90ee3991fb1c2f09bbe Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Sep 2021 15:41:27 +1000 Subject: [PATCH 632/774] Reduce GC when bashing on ownText during matching The for(obj : list) creates ArrayList Iterators, and those were making 20% of the GC now. The StringBuilder still dominates though. --- src/main/java/org/jsoup/nodes/Element.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 74381716bc..9d0c548b95 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1315,7 +1315,8 @@ public String ownText() { } private void ownText(StringBuilder accum) { - for (Node child : childNodes) { + for (int i = 0; i < childNodeSize(); i++) { + Node child = childNodes.get(i); if (child instanceof TextNode) { TextNode textNode = (TextNode) child; appendNormalisedText(accum, textNode); From 8fc3264c67db902cebe1f2185c11805c31eb34d3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Sep 2021 16:01:59 +1000 Subject: [PATCH 633/774] Use normalName to check br tags, for case insensitivity --- CHANGES | 3 +++ src/main/java/org/jsoup/nodes/Element.java | 4 ++-- src/test/java/org/jsoup/nodes/ElementTest.java | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 4e8c82de8d..9bd97ff7a8 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,9 @@ jsoup changelog * Bugfix: the Attributes::equals() method was sensitive to the order of its contents, but it should not be. <https://github.com/jhy/jsoup/issues/1492> + * Bugfix: when the HTML parser was configured to preserve case, Element text methods would miss adding whitespace for + "BR" tags. + * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods. <https://github.com/jhy/jsoup/issues/1628> diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 9d0c548b95..230790d4ea 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1253,7 +1253,7 @@ public void head(Node node, int depth) { } else if (node instanceof Element) { Element element = (Element) node; if (accum.length() > 0 && - (element.isBlock() || element.tag.getName().equals("br")) && + (element.isBlock() || element.tag.normalName().equals("br")) && !TextNode.lastCharIsWhitespace(accum)) accum.append(' '); } @@ -1336,7 +1336,7 @@ private static void appendNormalisedText(StringBuilder accum, TextNode textNode) } private static void appendWhitespaceIfBr(Element element, StringBuilder accum) { - if (element.tag.getName().equals("br") && !TextNode.lastCharIsWhitespace(accum)) + if (element.tag.normalName().equals("br") && !TextNode.lastCharIsWhitespace(accum)) accum.append(" "); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 08a657deca..52afb83092 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; @@ -161,6 +162,16 @@ public void testBrHasSpace() { assertEquals("Hello there", doc.text()); } + @Test + public void testBrHasSpaceCaseSensitive() { + Document doc = Jsoup.parse("<p>Hello<br>there<BR>now</p>", Parser.htmlParser().settings(ParseSettings.preserveCase)); + assertEquals("Hello there now", doc.text()); + assertEquals("Hello there now", doc.select("p").first().ownText()); + + doc = Jsoup.parse("<p>Hello <br> there <BR> now</p>"); + assertEquals("Hello there now", doc.text()); + } + @Test public void testWholeText() { Document doc = Jsoup.parse("<p> Hello\nthere &nbsp; </p>"); From c42257ffc4a97010e911a8f7886d49b8b23d969a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 16 Sep 2021 09:36:36 +1000 Subject: [PATCH 634/774] Make BooleanAttribute check public --- src/main/java/org/jsoup/nodes/Attribute.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 1e0f77e8c4..9ac1623009 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -183,7 +183,7 @@ protected static boolean shouldCollapseAttribute(final String key, @Nullable fin /** * Checks if this attribute name is defined as a boolean attribute in HTML5 */ - protected static boolean isBooleanAttribute(final String key) { + public static boolean isBooleanAttribute(final String key) { return Arrays.binarySearch(booleanAttributes, key) >= 0; } From 6a43590f7f59685fdc9838ff32ec5e0f376f9f85 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 16 Sep 2021 12:50:32 +1000 Subject: [PATCH 635/774] Make sure attribute names are syntactically correct when serializing For both HTML and XML variants. Shared impl for jsoup html() and W3CDom conversion. Also removed Attributes.html() inline method as we're already making an Attribute method call now. Fixes #1474 --- CHANGES | 5 +++ src/main/java/org/jsoup/helper/W3CDom.java | 10 ++--- src/main/java/org/jsoup/nodes/Attribute.java | 37 ++++++++++++++++--- src/main/java/org/jsoup/nodes/Attributes.java | 15 ++------ .../java/org/jsoup/parser/HtmlParserTest.java | 13 ++++++- .../org/jsoup/parser/XmlTreeBuilderTest.java | 8 ++++ 6 files changed, 63 insertions(+), 25 deletions(-) diff --git a/CHANGES b/CHANGES index 9bd97ff7a8..024d834c41 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,11 @@ jsoup changelog * Bugfix: when the HTML parser was configured to preserve case, Element text methods would miss adding whitespace for "BR" tags. + * Bugfix: attribute names are now normalized & validated correctly for the specific output syntax (HTML or XML). + Previously, syntactically invalid attribute names could be output by the html() methods. Such attributes are still + available in the DOM, and will be normalized if possible on output. + <https://github.com/jhy/jsoup/issues/1474> + * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods. <https://github.com/jhy/jsoup/issues/1628> diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 79ada4494a..8d38d74f1c 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -41,6 +41,7 @@ import java.util.regex.Pattern; import static javax.xml.transform.OutputKeys.METHOD; +import static org.jsoup.nodes.Document.OutputSettings.Syntax.xml; /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, @@ -340,15 +341,12 @@ public void tail(org.jsoup.nodes.Node source, int depth) { namespacesStack.pop(); } - private static final Pattern attrKeyReplace = Pattern.compile("[^-a-zA-Z0-9_:.]"); - private static final Pattern attrKeyValid = Pattern.compile("[a-zA-Z_:][-a-zA-Z0-9_:.]*"); - private void copyAttributes(org.jsoup.nodes.Node source, Element el) { for (Attribute attribute : source.attributes()) { - // valid xml attribute names are: ^[a-zA-Z_:][-a-zA-Z0-9_:.] - String key = attrKeyReplace.matcher(attribute.getKey()).replaceAll(""); - if (attrKeyValid.matcher(key).matches()) + String key = Attribute.getValidKey(attribute.getKey(), xml); + if (key != null) { // null if couldn't be coerced to validity el.setAttribute(key, attribute.getValue()); + } } } diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 9ac1623009..a1b2ae21ec 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -1,13 +1,15 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; +import org.jsoup.nodes.Document.OutputSettings.Syntax; import javax.annotation.Nullable; import java.io.IOException; import java.util.Arrays; import java.util.Map; +import java.util.regex.Pattern; /** A single key + value attribute. (Only used for presentation.) @@ -122,7 +124,18 @@ public String html() { return StringUtil.releaseBuilder(sb); } + protected void html(Appendable accum, Document.OutputSettings out) throws IOException { + html(key, val, accum, out); + } + protected static void html(String key, @Nullable String val, Appendable accum, Document.OutputSettings out) throws IOException { + key = getValidKey(key, out.syntax()); + if (key == null) return; // can't write it :( + htmlNoValidate(key, val, accum, out); + } + + static void htmlNoValidate(String key, @Nullable String val, Appendable accum, Document.OutputSettings out) throws IOException { + // structured like this so that Attributes can check we can write first, so it can add whitespace correctly accum.append(key); if (!shouldCollapseAttribute(key, val, out)) { accum.append("=\""); @@ -130,9 +143,23 @@ protected static void html(String key, @Nullable String val, Appendable accum, D accum.append('"'); } } - - protected void html(Appendable accum, Document.OutputSettings out) throws IOException { - html(key, val, accum, out); + + private static final Pattern xmlKeyValid = Pattern.compile("[a-zA-Z_:][-a-zA-Z0-9_:.]*"); + private static final Pattern xmlKeyReplace = Pattern.compile("[^-a-zA-Z0-9_:.]"); + private static final Pattern htmlKeyValid = Pattern.compile("[^\\x00-\\x1f\\x7f-\\x9f \"'/=]+"); + private static final Pattern htmlKeyReplace = Pattern.compile("[\\x00-\\x1f\\x7f-\\x9f \"'/=]"); + + @Nullable public static String getValidKey(String key, Syntax syntax) { + // we consider HTML attributes to always be valid. XML checks key validity + if (syntax == Syntax.xml && !xmlKeyValid.matcher(key).matches()) { + key = xmlKeyReplace.matcher(key).replaceAll(""); + return xmlKeyValid.matcher(key).matches() ? key : null; // null if could not be coerced + } + else if (syntax == Syntax.html && !htmlKeyValid.matcher(key).matches()) { + key = htmlKeyReplace.matcher(key).replaceAll(""); + return htmlKeyValid.matcher(key).matches() ? key : null; // null if could not be coerced + } + return key; } /** @@ -176,7 +203,7 @@ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { // collapse unknown foo=null, known checked=null, checked="", checked=checked; write out others protected static boolean shouldCollapseAttribute(final String key, @Nullable final String val, final Document.OutputSettings out) { return ( - out.syntax() == Document.OutputSettings.Syntax.html && + out.syntax() == Syntax.html && (val == null || (val.isEmpty() || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); } diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index fc442ef822..97c4b958a8 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -350,18 +350,9 @@ final void html(final Appendable accum, final Document.OutputSettings out) throw for (int i = 0; i < sz; i++) { if (isInternalKey(keys[i])) continue; - - // inlined from Attribute.html() - final String key = keys[i]; - final String val = vals[i]; - accum.append(' ').append(key); - - // collapse checked=null, checked="", checked=checked; write out others - if (!Attribute.shouldCollapseAttribute(key, val, out)) { - accum.append("=\""); - Entities.escape(accum, val == null ? EmptyString : val, out, true, false, false); - accum.append('"'); - } + final String key = Attribute.getValidKey(keys[i], out.syntax()); + if (key != null) + Attribute.htmlNoValidate(key, vals[i], accum.append(' '), out); } } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 33e5f9e9d1..95cf165f83 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -67,13 +67,13 @@ public class HtmlParserTest { @Test public void parsesQuiteRoughAttributes() { String html = "<p =a>One<a <p>Something</p>Else"; - // this (used to; now gets cleaner) gets a <p> with attr '=a' and an <a tag with an attribue named '<p'; and then auto-recreated + // this (used to; now gets cleaner) gets a <p> with attr '=a' and an <a tag with an attribute named '<p'; and then auto-recreated Document doc = Jsoup.parse(html); // NOTE: per spec this should be the test case. but impacts too many ppl // assertEquals("<p =a>One<a <p>Something</a></p>\n<a <p>Else</a>", doc.body().html()); - assertEquals("<p =a>One<a></a></p><p><a>Something</a></p><a>Else</a>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<p a>One<a></a></p><p><a>Something</a></p><a>Else</a>", TextUtil.stripNewlines(doc.body().html())); doc = Jsoup.parse("<p .....>"); assertEquals("<p .....></p>", doc.body().html()); @@ -1490,4 +1490,13 @@ private boolean didAddElements(String input) { assertEquals("&lt;" + tag + "&gt;Text<!--/" + tag + "-->", doc.body().html()); } } + + @Test void htmlOutputCorrectsInvalidAttributeNames() { + String html = "<body style=\"color: red\" \" name\"><div =\"\"></div></body>"; + Document doc = Jsoup.parse(html); + assertEquals(Document.OutputSettings.Syntax.html, doc.outputSettings().syntax()); + + String out = doc.body().outerHtml(); + assertEquals("<body style=\"color: red\" name>\n <div></div>\n</body>", out); + } } diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 8fb0fbd8a8..8cd6bdd53c 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -290,4 +290,12 @@ public void handlesLTinScript() { assertEquals("<p one=\"&lt;two>&copy;\">Three</p>", doc.html()); } + @Test void xmlOutputCorrectsInvalidAttributeNames() { + String xml = "<body style=\"color: red\" \" name\"><div =\"\"></div></body>"; + Document doc = Jsoup.parse(xml, Parser.xmlParser()); + assertEquals(Syntax.xml, doc.outputSettings().syntax()); + + String out = doc.html(); + assertEquals("<body style=\"color: red\" name=\"\"><div></div></body>", out); + } } From d075651d59bd0565f718077bfe4e3c6f543f477b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 16 Sep 2021 14:31:47 +1000 Subject: [PATCH 636/774] Changelog typo --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 024d834c41..7a356e68ae 100644 --- a/CHANGES +++ b/CHANGES @@ -15,7 +15,7 @@ jsoup changelog * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. - * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of java.annotation (used as a build-time + * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of javax.annotation (used as a build-time dependency for nullability assertions). <https://github.com/jhy/jsoup/issues/1616> From a049edcdae373ef6c24747845adb846d1298cb72 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 17 Sep 2021 19:25:07 +1000 Subject: [PATCH 637/774] Added full parser support for template tags (#1635) Fixes #1634 --- CHANGES | 3 + .../org/jsoup/parser/HtmlTreeBuilder.java | 88 ++++++-- .../jsoup/parser/HtmlTreeBuilderState.java | 205 ++++++++++++++---- src/main/java/org/jsoup/parser/Tag.java | 2 +- .../java/org/jsoup/parser/HtmlParserTest.java | 94 ++++++++ .../parser/HtmlTreeBuilderStateTest.java | 2 +- .../org/jsoup/parser/HtmlTreeBuilderTest.java | 2 +- 7 files changed, 334 insertions(+), 62 deletions(-) diff --git a/CHANGES b/CHANGES index 7a356e68ae..893e00167e 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ jsoup changelog * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> + * Improvement: added full support for the <template> tag to the HTML5 parser spec. + <https://github.com/jhy/jsoup/issues/1634> + * Improvement: added support in CharacterReader to track newlines, so that parse errors can be reported more intuitively. <https://github.com/jhy/jsoup/pull/1624> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 8c7c8ce294..ff55f91c79 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -32,6 +32,7 @@ public class HtmlTreeBuilder extends TreeBuilder { static final String[] TagSearchTableScope = new String[]{"html", "table"}; static final String[] TagSearchSelectScope = new String[]{"optgroup", "option"}; static final String[] TagSearchEndTags = new String[]{"dd", "dt", "li", "optgroup", "option", "p", "rb", "rp", "rt", "rtc"}; + static final String[] TagThoroughSearchEndTags = new String[]{"caption", "colgroup", "dd", "dt", "li", "optgroup", "option", "p", "rb", "rp", "rt", "rtc", "tbody", "td", "tfoot", "th", "thead", "tr"}; static final String[] TagSearchSpecial = new String[]{"address", "applet", "area", "article", "aside", "base", "basefont", "bgsound", "blockquote", "body", "br", "button", "caption", "center", "col", "colgroup", "command", "dd", "details", "dir", "div", "dl", "dt", "embed", "fieldset", "figcaption", "figure", "footer", "form", @@ -51,6 +52,7 @@ public class HtmlTreeBuilder extends TreeBuilder { private @Nullable FormElement formElement; // the current form element private @Nullable Element contextElement; // fragment parse context -- could be null even if fragment parsing private ArrayList<Element> formattingElements; // active (open) formatting elements + private ArrayList<HtmlTreeBuilderState> tmplInsertMode; // stack of Template Insertion modes private List<String> pendingTableCharacters; // chars in table to be shifted out private Token.EndTag emptyEnd; // reused empty end tag @@ -79,6 +81,7 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { formElement = null; contextElement = null; formattingElements = new ArrayList<>(); + tmplInsertMode = new ArrayList<>(); pendingTableCharacters = new ArrayList<>(); emptyEnd = new Token.EndTag(); framesetOk = true; @@ -109,13 +112,15 @@ else if (contextTag.equals("script")) else if (contextTag.equals(("noscript"))) tokeniser.transition(TokeniserState.Data); // if scripting enabled, rawtext else if (contextTag.equals("plaintext")) - tokeniser.transition(TokeniserState.Data); + tokeniser.transition(TokeniserState.PLAINTEXT); else tokeniser.transition(TokeniserState.Data); // default root = new Element(Tag.valueOf(contextTag, settings), baseUri); doc.appendChild(root); stack.add(root); + if (contextTag.equals("template")) + pushTemplateMode(HtmlTreeBuilderState.InTemplate); resetInsertionMode(); // setup form element to nearest form on context (up ancestor chain). ensures form controls are associated @@ -258,10 +263,15 @@ Element insertEmpty(Token.StartTag startTag) { return el; } - FormElement insertForm(Token.StartTag startTag, boolean onStack) { + FormElement insertForm(Token.StartTag startTag, boolean onStack, boolean checkTemplateStack) { Tag tag = Tag.valueOf(startTag.name(), settings); FormElement el = new FormElement(tag, null, settings.normalizeAttributes(startTag.attributes)); - setFormElement(el); + if (checkTemplateStack) { + if(!onStack("template")) + setFormElement(el); + } else + setFormElement(el); + insertNode(el); if (onStack) stack.add(el); @@ -318,11 +328,15 @@ ArrayList<Element> getStack() { } boolean onStack(Element el) { - return isElementInQueue(stack, el); + return onStack(stack, el); + } + + boolean onStack(String elName) { + return getFromStack(elName) != null; } private static final int maxQueueDepth = 256; // an arbitrary tension point between real HTML and crafted pain - private boolean isElementInQueue(ArrayList<Element> queue, Element element) { + private static boolean onStack(ArrayList<Element> queue, Element element) { final int bottom = queue.size() - 1; final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; for (int pos = bottom; pos >= upper; pos--) { @@ -391,7 +405,7 @@ void popStackToBefore(String elName) { } void clearStackToTableContext() { - clearStackToContext("table"); + clearStackToContext("table", "template"); } void clearStackToTableBodyContext() { @@ -412,7 +426,7 @@ private void clearStackToContext(String... nodeNames) { } } - Element aboveOnStack(Element el) { + @Nullable Element aboveOnStack(Element el) { assert onStack(el); for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); @@ -461,7 +475,7 @@ void resetInsertionMode() { transition(HtmlTreeBuilderState.InSelect); // todo - should loop up (with some limit) and check for table or template hits break; - } else if (("td".equals(name) || "th".equals(name) && !last)) { + } else if ((("td".equals(name) || "th".equals(name)) && !last)) { transition(HtmlTreeBuilderState.InCell); break; } else if ("tr".equals(name)) { @@ -479,7 +493,11 @@ void resetInsertionMode() { } else if ("table".equals(name)) { transition(HtmlTreeBuilderState.InTable); break; - // todo - template + } else if ("template".equals(name)) { + HtmlTreeBuilderState tmplState = currentTemplateMode(); + Validate.notNull(tmplState, "Bug: no template insertion mode on stack!"); + transition(tmplState); + break; } else if ("head".equals(name) && !last) { transition(HtmlTreeBuilderState.InHead); break; @@ -598,10 +616,12 @@ List<String> getPendingTableCharacters() { } /** - 11.2.5.2 Closing elements that have implied end tags<p/> - When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a - dt element, an li element, an option element, an optgroup element, a p element, an rp element, or an rt element, - the UA must pop the current node off the stack of open elements. + 13.2.6.3 Closing elements that have implied end tags + When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a dt element, an li element, an optgroup element, an option element, a p element, an rb element, an rp element, an rt element, or an rtc element, the UA must pop the current node off the stack of open elements. + + If a step requires the UA to generate implied end tags but lists an element to exclude from the process, then the UA must perform the above steps as if that element was not in the above list. + + When the steps below require the UA to generate all implied end tags thoroughly, then, while the current node is a caption element, a colgroup element, a dd element, a dt element, an li element, an optgroup element, an option element, a p element, an rb element, an rp element, an rt element, an rtc element, a tbody element, a td element, a tfoot element, a th element, a thead element, or a tr element, the UA must pop the current node off the stack of open elements. @param excludeTag If a step requires the UA to generate implied end tags but lists an element to exclude from the process, then the UA must perform the above steps as if that element was not in the above list. @@ -615,7 +635,24 @@ void generateImpliedEndTags(String excludeTag) { } void generateImpliedEndTags() { - generateImpliedEndTags(null); + generateImpliedEndTags(false); + } + + /** + Pops elements off the stack according to the implied end tag rules + @param thorough if we are thorough (includes table elements etc) or not + */ + void generateImpliedEndTags(boolean thorough) { + final String[] search = thorough ? TagThoroughSearchEndTags : TagSearchEndTags; + while (inSorted(currentElement().normalName(), search)) { + pop(); + } + } + + void closeElement(String name) { + generateImpliedEndTags(name); + if (!name.equals(currentElement().normalName())) error(state()); + popStackToClose(name); } boolean isSpecial(Element el) { @@ -746,7 +783,7 @@ void removeFromActiveFormattingElements(Element el) { } boolean isInActiveFormattingElements(Element el) { - return isElementInQueue(formattingElements, el); + return onStack(formattingElements, el); } Element getActiveFormattingElement(String nodeName) { @@ -790,6 +827,27 @@ void insertInFosterParent(Node in) { fosterParent.appendChild(in); } + // Template Insertion Mode stack + void pushTemplateMode(HtmlTreeBuilderState state) { + tmplInsertMode.add(state); + } + + @Nullable HtmlTreeBuilderState popTemplateMode() { + if (tmplInsertMode.size() > 0) { + return tmplInsertMode.remove(tmplInsertMode.size() -1); + } else { + return null; + } + } + + int templateModeSize() { + return tmplInsertMode.size(); + } + + @Nullable HtmlTreeBuilderState currentTemplateMode() { + return (tmplInsertMode.size() > 0) ? tmplInsertMode.get(tmplInsertMode.size() -1) : null; + } + @Override public String toString() { return "TreeBuilder{" + diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index dedcb001a6..57c27ce3b8 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -135,7 +135,6 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.transition(InHeadNoscript); } else if (name.equals("script")) { // skips some script rules as won't execute them - tb.tokeniser.transition(TokeniserState.ScriptData); tb.markInsertionMode(); tb.transition(Text); @@ -143,6 +142,12 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (name.equals("head")) { tb.error(this); return false; + } else if (name.equals("template")) { + tb.insert(start); + tb.insertMarkerToFormattingElements(); + tb.framesetOk(false); + tb.transition(InTemplate); + tb.pushTemplateMode(InTemplate); } else { return anythingElse(t, tb); } @@ -155,7 +160,19 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.transition(AfterHead); } else if (inSorted(name, Constants.InHeadEnd)) { return anythingElse(t, tb); - } else { + } else if (name.equals("template")) { + if (!tb.onStack(name)) { + tb.error(this); + } else { + tb.generateImpliedEndTags(true); + if (!name.equals(tb.currentElement().normalName())) tb.error(this); + tb.popStackToClose(name); + tb.clearFormattingElementsToLastMarker(); + tb.popTemplateMode(); + tb.resetInsertionMode(); + } + } + else { tb.error(this); return false; } @@ -236,9 +253,13 @@ boolean process(Token t, HtmlTreeBuilder tb) { anythingElse(t, tb); } } else if (t.isEndTag()) { - if (inSorted(t.asEndTag().normalName(), AfterHeadBody)) { + String name = t.asEndTag().normalName(); + if (inSorted(name, AfterHeadBody)) { anythingElse(t, tb); - } else { + } else if (name.equals("template")) { + tb.process(t, InHead); + } + else { tb.error(this); return false; } @@ -286,6 +307,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { case EndTag: return inBodyEndTag(t, tb); case EOF: + if (tb.templateModeSize() > 0) + return tb.process(t, InTemplate); // todo: error if stack contains something not dd, dt, li, p, tbody, td, tfoot, th, thead, tr, body, html // stop parsing break; @@ -340,7 +363,8 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { break; case "html": tb.error(this); - // merge attributes onto real html (if present) + if (tb.onStack("template")) return false; // ignore + // otherwise, merge attributes onto real html (if present) stack = tb.getStack(); if (stack.size() > 0) { Element html = tb.getStack().get(0); @@ -355,7 +379,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { case "body": tb.error(this); stack = tb.getStack(); - if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { + if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body")) || tb.onStack("template")) { // only in fragment case return false; // ignore } else { @@ -389,14 +413,14 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { } break; case "form": - if (tb.getFormElement() != null) { + if (tb.getFormElement() != null && !tb.onStack("template")) { tb.error(this); return false; } if (tb.inButtonScope("p")) { - tb.processEndTag("p"); + tb.closeElement("p"); } - tb.insertForm(startTag, true); + tb.insertForm(startTag, true, true); // won't associate to any template break; case "plaintext": if (tb.inButtonScope("p")) { @@ -641,6 +665,9 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { final String name = endTag.normalName(); switch (name) { + case "template": + tb.process(t, InHead); + break; case "sarcasm": // *sigh* case "span": // same as final fall through, but saves short circuit @@ -671,17 +698,26 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return tb.process(endTag); break; case "form": - Element currentForm = tb.getFormElement(); - tb.setFormElement(null); - if (currentForm == null || !tb.inScope(name)) { - tb.error(this); - return false; - } else { + if (!tb.onStack("template")) { + Element currentForm = tb.getFormElement(); + tb.setFormElement(null); + if (currentForm == null || !tb.inScope(name)) { + tb.error(this); + return false; + } tb.generateImpliedEndTags(); if (!tb.currentElementIs(name)) tb.error(this); // remove currentForm from stack. will shift anything under up. tb.removeFromStack(currentForm); + } else { // template on stack + if (!tb.inScope(name)) { + tb.error(this); + return false; + } + tb.generateImpliedEndTags(); + if (!tb.currentElementIs(name)) tb.error(this); + tb.popStackToClose(name); } break; case "p": @@ -974,10 +1010,10 @@ boolean process(Token t, HtmlTreeBuilder tb) { } } else if (name.equals("form")) { tb.error(this); - if (tb.getFormElement() != null) + if (tb.getFormElement() != null || tb.onStack("template")) return false; else { - tb.insertForm(startTag, false); + tb.insertForm(startTag, false, false); // not added to stack. can associate to template } } else { return anythingElse(t, tb); @@ -998,6 +1034,8 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (inSorted(name, InTableEndErr)) { tb.error(this); return false; + } else if (name.equals("template")) { + tb.process(t, InHead); } else { return anythingElse(t, tb); } @@ -1106,22 +1144,32 @@ boolean process(Token t, HtmlTreeBuilder tb) { case "col": tb.insertEmpty(startTag); break; + case "template": + tb.process(t, InHead); + break; default: return anythingElse(t, tb); } break; case EndTag: Token.EndTag endTag = t.asEndTag(); - if (endTag.normalName.equals("colgroup")) { - if (tb.currentElementIs("html")) { // frag case - tb.error(this); - return false; - } else { - tb.pop(); - tb.transition(InTable); - } - } else - return anythingElse(t, tb); + String name = endTag.normalName(); + switch (name) { + case "colgroup": + if (!tb.currentElementIs(name)) { + tb.error(this); + return false; + } else { + tb.pop(); + tb.transition(InTable); + } + break; + case "template": + tb.process(t, InHead); + break; + default: + return anythingElse(t, tb); + } break; case EOF: if (tb.currentElementIs("html")) @@ -1134,10 +1182,14 @@ boolean process(Token t, HtmlTreeBuilder tb) { return true; } - private boolean anythingElse(Token t, TreeBuilder tb) { - boolean processed = tb.processEndTag("colgroup"); - if (processed) // only ignored in frag case - return tb.process(t); + private boolean anythingElse(Token t, HtmlTreeBuilder tb) { + if (!tb.currentElementIs("colgroup")) { + tb.error(this); + return false; + } + tb.pop(); + tb.transition(InTable); + tb.process(t); return true; } }, @@ -1147,9 +1199,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { case StartTag: Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); - if (name.equals("template")) { - tb.insert(startTag); - } else if (name.equals("tr")) { + if (name.equals("tr")) { tb.clearStackToTableBodyContext(); tb.insert(startTag); tb.transition(InRow); @@ -1209,9 +1259,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); - if (name.equals("template")) { - tb.insert(startTag); - } else if (inSorted(name, InCellNames)) { + if (inSorted(name, InCellNames)) { tb.clearStackToTableRowContext(); tb.insert(startTag); tb.transition(InCell); @@ -1365,7 +1413,7 @@ else if (name.equals("option")) { return false; // frag tb.processEndTag("select"); return tb.process(start); - } else if (name.equals("script")) { + } else if (name.equals("script") || name.equals("template")) { return tb.process(t, InHead); } else { return anythingElse(t, tb); @@ -1398,6 +1446,8 @@ else if (name.equals("option")) { tb.resetInsertionMode(); } break; + case "template": + return tb.process(t, InHead); default: return anythingElse(t, tb); } @@ -1419,12 +1469,12 @@ private boolean anythingElse(Token t, HtmlTreeBuilder tb) { }, InSelectInTable { boolean process(Token t, HtmlTreeBuilder tb) { - if (t.isStartTag() && inSorted(t.asStartTag().normalName(), InSelecTableEnd)) { + if (t.isStartTag() && inSorted(t.asStartTag().normalName(), InSelectTableEnd)) { tb.error(this); tb.popStackToClose("select"); tb.resetInsertionMode(); return tb.process(t); - } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(),InSelecTableEnd )) { + } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(), InSelectTableEnd)) { tb.error(this); if (tb.inTableScope(t.asEndTag().normalName())) { tb.popStackToClose("select"); @@ -1437,6 +1487,71 @@ boolean process(Token t, HtmlTreeBuilder tb) { } } }, + InTemplate { + boolean process(Token t, HtmlTreeBuilder tb) { + final String name; + switch (t.type) { + case Character: + case Comment: + case Doctype: + tb.process(t, InBody); + break; + case StartTag: + name = t.asStartTag().normalName(); + if (inSorted(name, InTemplateToHead)) + tb.process(t, InHead); + else if (inSorted(name, InTemplateToTable)) { + tb.popTemplateMode(); + tb.pushTemplateMode(InTable); + tb.transition(InTable); + return tb.process(t); + } + else if (name.equals("col")) { + tb.popTemplateMode(); + tb.pushTemplateMode(InColumnGroup); + tb.transition(InColumnGroup); + return tb.process(t); + } else if (name.equals("tr")) { + tb.popTemplateMode(); + tb.pushTemplateMode(InTableBody); + tb.transition(InTableBody); + return tb.process(t); + } else if (name.equals("td") || name.equals("th")) { + tb.popTemplateMode(); + tb.pushTemplateMode(InRow); + tb.transition(InRow); + return tb.process(t); + } else { + tb.popTemplateMode(); + tb.pushTemplateMode(InBody); + tb.transition(InBody); + return tb.process(t); + } + + break; + case EndTag: + name = t.asEndTag().normalName(); + if (name.equals("template")) + tb.process(t, InHead); + else { + tb.error(this); + return false; + } + break; + case EOF: + if (!tb.onStack("template")) {// stop parsing + return true; + } + tb.error(this); + tb.popStackToClose("template"); + tb.clearFormattingElementsToLastMarker(); + tb.popTemplateMode(); + tb.resetInsertionMode(); + return tb.process(t); + } + return true; + } + }, AfterBody { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { @@ -1623,10 +1738,10 @@ static final class Constants { static final String[] InHeadEmpty = new String[]{"base", "basefont", "bgsound", "command", "link"}; static final String[] InHeadRaw = new String[]{"noframes", "style"}; static final String[] InHeadEnd = new String[]{"body", "br", "html"}; - static final String[] AfterHeadBody = new String[]{"body", "html"}; + static final String[] AfterHeadBody = new String[]{"body", "br", "html"}; static final String[] BeforeHtmlToHead = new String[]{"body", "br", "head", "html", }; static final String[] InHeadNoScriptHead = new String[]{"basefont", "bgsound", "link", "meta", "noframes", "style"}; - static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"}; + static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "template", "title"}; static final String[] InBodyStartPClosers = new String[]{"address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", "p", "section", "summary", "ul"}; @@ -1646,7 +1761,7 @@ static final class Constants { static final String[] InBodyEndTableFosters = new String[]{"table", "tbody", "tfoot", "thead", "tr"}; static final String[] InTableToBody = new String[]{"tbody", "tfoot", "thead"}; static final String[] InTableAddBody = new String[]{"td", "th", "tr"}; - static final String[] InTableToHead = new String[]{"script", "style"}; + static final String[] InTableToHead = new String[]{"script", "style", "template"}; static final String[] InCellNames = new String[]{"td", "th"}; static final String[] InCellBody = new String[]{"body", "caption", "col", "colgroup", "html"}; static final String[] InCellTable = new String[]{ "table", "tbody", "tfoot", "thead", "tr"}; @@ -1658,9 +1773,11 @@ static final class Constants { static final String[] InRowMissing = new String[]{"caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr"}; static final String[] InRowIgnore = new String[]{"body", "caption", "col", "colgroup", "html", "td", "th"}; static final String[] InSelectEnd = new String[]{"input", "keygen", "textarea"}; - static final String[] InSelecTableEnd = new String[]{"caption", "table", "tbody", "td", "tfoot", "th", "thead", "tr"}; + static final String[] InSelectTableEnd = new String[]{"caption", "table", "tbody", "td", "tfoot", "th", "thead", "tr"}; static final String[] InTableEndIgnore = new String[]{"tbody", "tfoot", "thead"}; static final String[] InHeadNoscriptIgnore = new String[]{"head", "noscript"}; static final String[] InCaptionIgnore = new String[]{"body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr"}; + static final String[] InTemplateToHead = new String[] {"base", "basefont", "bgsound", "link", "meta", "noframes", "script", "style", "template", "title"}; + static final String[] InTemplateToTable = new String[] {"caption", "colgroup", "tbody", "tfoot", "thead"}; } } diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 7bb84740d1..1f43ead474 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -237,7 +237,7 @@ protected Tag clone() { "ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins", "del", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th", "td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main", - "svg", "math", "center" + "svg", "math", "center", "template" }; private static final String[] inlineTags = { "object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd", diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 95cf165f83..59b25044dc 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1499,4 +1499,98 @@ private boolean didAddElements(String input) { String out = doc.body().outerHtml(); assertEquals("<body style=\"color: red\" name>\n <div></div>\n</body>", out); } + + @Test void templateInHead() { + // https://try.jsoup.org/~EGp3UZxQe503TJDHQEQEzm8IeUc + String html = "<head><template id=1><meta name=tmpl></template><title>Test</title><style>One</style> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>Two</p>"; + Document doc = Jsoup.parse(html); + + String want = "<html><head><template id=\"1\"><meta name=\"tmpl\"></template><title>Test</title><style>One</style> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>Two</p></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + + Elements template = doc.select("template#1"); + template.select("meta").attr("content", "Yes"); + template.unwrap(); + + want = "<html><head><meta name=\"tmpl\" content=\"Yes\"><title>Test</title><style>One</style> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>Two</p></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + } + + @Test void nestedTemplateInBody() { + String html = "<body><template id=1><table><tr><template id=2><td>One</td><td>Two</td></template></tr></template></body>"; + Document doc = Jsoup.parse(html); + + String want = "<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><template id=\"1\"><table><tbody><tr><template id=\"2\"><td>One</td><td>Two</td></template></tr></tbody></table></template></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + + // todo - will be nice to add some simpler template element handling like clone children etc? + Element tmplTbl = doc.selectFirst("template#1"); + Element tmplRow = doc.selectFirst("template#2"); + assertNotNull(tmplRow); + assertNotNull(tmplTbl); + tmplRow.appendChild(tmplRow.clone()); + doc.select("template").unwrap(); + + want = "<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><table><tbody><tr><td>One</td><td>Two</td><td>One</td><td>Two</td></tr></tbody></table></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + } + + @Test void canSelectIntoTemplate() { + String html = "<body><div><template><p>Hello</p>"; + Document doc = Jsoup.parse(html); + String want = "<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div><template><p>Hello</p></template></div></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + + Element p = doc.selectFirst("div p"); + Element p1 = doc.selectFirst("template :containsOwn(Hello)"); + assertEquals("p", p.normalName()); + assertEquals(p, p1); + } + + @Test void tableRowFragment() { + Document doc = Jsoup.parse("<body><table></table></body"); + String html = "<tr><td><img></td></tr>"; + Element table = doc.selectFirst("table"); + table.html(html); // invokes the fragment parser with table as context + String want = "<tbody><tr><td><img></td></tr></tbody>"; + assertEquals(want, TextUtil.stripNewlines(table.html())); + want = "<table><tbody><tr><td><img></td></tr></tbody></table>"; + assertEquals(want, TextUtil.stripNewlines(doc.body().html())); + } + + @Test void templateTableRowFragment() { + // https://github.com/jhy/jsoup/issues/1409 (per the fragment <tr> use case) + Document doc = Jsoup.parse("<body><table><template></template></table></body"); + String html = "<tr><td><img></td></tr>"; + Element tmpl = doc.selectFirst("template"); + tmpl.html(html); // invokes the fragment parser with template as context + String want = "<tr><td><img></td></tr>"; + assertEquals(want, TextUtil.stripNewlines(tmpl.html())); + tmpl.unwrap(); + + want = "<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><table><tr><td><img></td></tr></table></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + } + + @Test void templateNotInTableRowFragment() { + // https://github.com/jhy/jsoup/issues/1409 (per the fragment <tr> use case) + Document doc = Jsoup.parse("<body><template></template></body"); + String html = "<tr><td><img></td></tr>"; + Element tmpl = doc.selectFirst("template"); + tmpl.html(html); // invokes the fragment parser with template as context + String want = "<tr><td><img></td></tr>"; + assertEquals(want, TextUtil.stripNewlines(tmpl.html())); + tmpl.unwrap(); + + want = "<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><tr><td><img></td></tr></body></html>"; + assertEquals(want, TextUtil.stripNewlines(doc.html())); + } + + @Test void templateFragment() { + // https://github.com/jhy/jsoup/issues/1315 + String html = "<template id=\"lorem-ipsum\"><tr><td>Lorem</td><td>Ipsum</td></tr></template>"; + Document frag = Jsoup.parseBodyFragment(html); + String want = "<template id=\"lorem-ipsum\"><tr><td>Lorem</td><td>Ipsum</td></tr></template>"; + assertEquals(want, TextUtil.stripNewlines(frag.body().html())); + } } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 649340c358..29482c8428 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -44,7 +44,7 @@ static void ensureSorted(List<Object[]> constants) { public void ensureArraysAreSorted() { List<Object[]> constants = findConstantArrays(Constants.class); ensureSorted(constants); - assertEquals(38, constants.size()); + assertEquals(40, constants.size()); } diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java index 133af5fb1a..10e6c254e9 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java @@ -18,7 +18,7 @@ public class HtmlTreeBuilderTest { public void ensureSearchArraysAreSorted() { List<Object[]> constants = HtmlTreeBuilderStateTest.findConstantArrays(HtmlTreeBuilder.class); HtmlTreeBuilderStateTest.ensureSorted(constants); - assertEquals(7, constants.size()); + assertEquals(8, constants.size()); } @Test From ec9847624f75ee485247f1173cafa20eb4a385a8 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sat, 18 Sep 2021 18:33:46 +1000 Subject: [PATCH 638/774] Normalize the query in :contains and :containsOwn selectors Addresses #876 --- CHANGES | 4 ++++ src/main/java/org/jsoup/internal/Normalizer.java | 3 +++ src/main/java/org/jsoup/internal/StringUtil.java | 2 +- src/main/java/org/jsoup/select/Evaluator.java | 8 +++++--- src/main/java/org/jsoup/select/Selector.java | 6 +++--- src/test/java/org/jsoup/select/SelectorTest.java | 16 ++++++++++++++++ 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 893e00167e..9ada384bdb 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,10 @@ jsoup changelog * Improvement: speed and memory optimizations for the :has(subquery) selector. + * Improvement: the :contains(text) and :containsOwn(text) selectors are now whitespace normalized, aligning to the + document text that they are matching against. + <https://github.com/jhy/jsoup/issues/876> + * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. diff --git a/src/main/java/org/jsoup/internal/Normalizer.java b/src/main/java/org/jsoup/internal/Normalizer.java index 305a0102bb..cf3208625a 100644 --- a/src/main/java/org/jsoup/internal/Normalizer.java +++ b/src/main/java/org/jsoup/internal/Normalizer.java @@ -7,14 +7,17 @@ */ public final class Normalizer { + /** Drops the input string to lower case. */ public static String lowerCase(final String input) { return input != null ? input.toLowerCase(Locale.ENGLISH) : ""; } + /** Lower-cases and trims the input string. */ public static String normalize(final String input) { return lowerCase(input).trim(); } + /** If a string literal, just lower case the string; otherwise lower-case and trim. */ public static String normalize(final String input, boolean isStringLiteral) { return isStringLiteral ? lowerCase(input) : normalize(input); } diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 08352253fa..eed2422062 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -193,7 +193,7 @@ public static boolean isInvisibleChar(int c) { /** * Normalise the whitespace within this string; multiple spaces collapse to a single, and all whitespace characters - * (e.g. newline, tab) convert to a simple space + * (e.g. newline, tab) convert to a simple space. * @param string content to normalise * @return normalised string */ diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index e086be3740..52cd432099 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -1,6 +1,7 @@ package org.jsoup.select; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; @@ -16,6 +17,7 @@ import static org.jsoup.internal.Normalizer.lowerCase; import static org.jsoup.internal.Normalizer.normalize; +import static org.jsoup.internal.StringUtil.normaliseWhitespace; /** @@ -665,7 +667,7 @@ public static final class ContainsText extends Evaluator { private final String searchText; public ContainsText(String searchText) { - this.searchText = lowerCase(searchText); + this.searchText = lowerCase(normaliseWhitespace(searchText)); } @Override @@ -691,7 +693,7 @@ public ContainsData(String searchText) { @Override public boolean matches(Element root, Element element) { - return lowerCase(element.data()).contains(searchText); + return lowerCase(element.data()).contains(searchText); // not whitespace normalized } @Override @@ -707,7 +709,7 @@ public static final class ContainsOwnText extends Evaluator { private final String searchText; public ContainsOwnText(String searchText) { - this.searchText = lowerCase(searchText); + this.searchText = lowerCase(normaliseWhitespace(searchText)); } @Override diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 0afd8c77df..511efbafc2 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -50,11 +50,11 @@ * <tr><td><code>:eq(<em>n</em>)</code></td><td>elements whose sibling index is equal to <em>n</em></td><td><code>td:eq(0)</code> finds the first cell of each row</td></tr> * <tr><td><code>:has(<em>selector</em>)</code></td><td>elements that contains at least one element matching the <em>selector</em></td><td><code>div:has(p)</code> finds <code>div</code>s that contain <code>p</code> elements.<br><code>div:has(&gt; a)</code> selects <code>div</code> elements that have at least one direct child <code>a</code> element.</td></tr> * <tr><td><code>:not(<em>selector</em>)</code></td><td>elements that do not match the <em>selector</em>. See also {@link Elements#not(String)}</td><td><code>div:not(.logo)</code> finds all divs that do not have the "logo" class.<p><code>div:not(:has(div))</code> finds divs that do not contain divs.</p></td></tr> - * <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contains the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants.</td><td><code>p:contains(jsoup)</code> finds p elements containing the text "jsoup".</td></tr> - * <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements whose text matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> + * <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contains the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants. The text is whitespace normalized. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:contains(jsoup)</code> finds p elements containing the text "jsoup".<p>{@code p:contains(hello \(there\) finds p elements containing the text "Hello (There)"}</p></td></tr> * <tr><td><code>:containsOwn(<em>text</em>)</code></td><td>elements that directly contain the specified text. The search is case insensitive. The text must appear in the found element, not any of its descendants.</td><td><code>p:containsOwn(jsoup)</code> finds p elements with own text "jsoup".</td></tr> - * <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesOwn(\\d+)</code> finds table cells directly containing digits. <code>div:matchesOwn((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> * <tr><td><code>:containsData(<em>data</em>)</code></td><td>elements that contains the specified <em>data</em>. The contents of {@code script} and {@code style} elements, and {@code comment} nodes (etc) are considered data nodes, not text nodes. The search is case insensitive. The data may appear in the found element, or any of its descendants.</td><td><code>script:contains(jsoup)</code> finds script elements containing the data "jsoup".</td></tr> + * <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements containing <b>whitespace normalized</b> text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> + * <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesOwn(\\d+)</code> finds table cells directly containing digits. <code>div:matchesOwn((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> * <tr><td></td><td>The above may be combined in any order and with other selectors</td><td><code>.light:contains(name):eq(0)</code></td></tr> * <tr><td><code>:matchText</code></td><td>treats text nodes as elements, and so allows you to match against and select text nodes.<p><b>Note</b> that using this selector will modify the DOM, so you may want to {@code clone} your document before using.</td><td>{@code p:matchText:firstChild} with input {@code <p>One<br />Two</p>} will return one {@link org.jsoup.nodes.PseudoTextElement} with text "{@code One}".</td></tr> * <tr><td colspan="3"><h3>Structural pseudo selectors</h3></td></tr> diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index eba28ee8c2..bedb41440b 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1012,4 +1012,20 @@ public void wildcardNamespaceMatchesNoNamespace() { assertEquals("One", nsEls.get(0).text()); assertEquals("Two", nsEls.get(1).text()); } + + @Test void containsTextQueryIsNormalized() { + Document doc = Jsoup.parse("<p><p id=1>Hello there now<em>!</em>"); + Elements a = doc.select("p:contains(Hello there now!)"); + Elements b = doc.select(":containsOwn(hello there now)"); + Elements c = doc.select("p:contains(Hello there now)"); + Elements d = doc.select(":containsOwn(hello There now)"); + Elements e = doc.select("p:contains(HelloThereNow)"); + + assertEquals(1, a.size()); + assertEquals(a, b); + assertEquals(a, c); + assertEquals(a, d); + assertEquals(0, e.size()); + assertNotEquals(a, e); + } } From 80c6ec7dbe424351193a29bb873476034e668057 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 10:14:24 +1000 Subject: [PATCH 639/774] Test formElement != null before copying isindex action As there will not be a form element pointer for inferred forms within template elements. --- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 6 ++++-- src/test/java/org/jsoup/parser/HtmlParserTest.java | 8 ++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index ff55f91c79..23529d53b4 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -599,7 +599,7 @@ void setFosterInserts(boolean fosterInserts) { this.fosterInserts = fosterInserts; } - FormElement getFormElement() { + @Nullable FormElement getFormElement() { return formElement; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 57c27ce3b8..fe8888379d 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -487,8 +487,10 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.processStartTag("form"); if (startTag.hasAttribute("action")) { Element form = tb.getFormElement(); - String action = startTag.attributes.get("action"); - form.attributes().put("action", action); // always LC, so don't need to scan up for ownerdoc + if (form != null && startTag.hasAttribute("action")) { + String action = startTag.attributes.get("action"); + form.attributes().put("action", action); // always LC, so don't need to scan up for ownerdoc + } } tb.processStartTag("hr"); tb.processStartTag("label"); diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 59b25044dc..b48d1415b3 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1593,4 +1593,12 @@ private boolean didAddElements(String input) { String want = "<template id=\"lorem-ipsum\"><tr><td>Lorem</td><td>Ipsum</td></tr></template>"; assertEquals(want, TextUtil.stripNewlines(frag.body().html())); } + + @Test void templateInferredForm() { + // https://github.com/jhy/jsoup/issues/1637 + Document doc = Jsoup.parse("<template><isindex action>"); + assertNotNull(doc); + assertEquals("<template><form><hr><label>This is a searchable index. Enter search keywords: <input name=\"isindex\"></label><hr></form></template>", + TextUtil.stripNewlines(doc.head().html())); + } } From 86602ebe8827631d291d56705cfd61f5ef513e62 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 10:41:34 +1000 Subject: [PATCH 640/774] Make sure Tag.valueOf and Token.StartTag both do the same name normalization Was allowing elements to be created without correctly processing the state leading to it. --- src/main/java/org/jsoup/parser/ParseSettings.java | 5 +++++ src/main/java/org/jsoup/parser/Token.java | 6 +++--- src/test/java/org/jsoup/parser/HtmlParserTest.java | 14 +++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index f94eecff1a..b83ac07764 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -79,4 +79,9 @@ Attributes normalizeAttributes(Attributes attributes) { } return attributes; } + + /** Returns the normal name that a Tag will have (trimmed and lower-cased) */ + static String normalName(String name) { + return lowerCase(name.trim()); + } } diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 9037b8633b..7f4296584e 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -178,7 +178,7 @@ final String toStringName() { final Tag name(String name) { tagName = name; - normalName = lowerCase(name); + normalName = ParseSettings.normalName(tagName); return this; } @@ -191,7 +191,7 @@ final void appendTagName(String append) { // might have null chars - need to replace with null replacement character append = append.replace(TokeniserState.nullChar, Tokeniser.replacementChar); tagName = tagName == null ? append : tagName.concat(append); - normalName = lowerCase(tagName); + normalName = ParseSettings.normalName(tagName); } final void appendTagName(char append) { @@ -283,7 +283,7 @@ Tag reset() { StartTag nameAttr(String name, Attributes attributes) { this.tagName = name; this.attributes = attributes; - normalName = lowerCase(tagName); + normalName = ParseSettings.normalName(tagName); return this; } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index b48d1415b3..78d7ca320a 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1595,10 +1595,22 @@ private boolean didAddElements(String input) { } @Test void templateInferredForm() { - // https://github.com/jhy/jsoup/issues/1637 + // https://github.com/jhy/jsoup/issues/1637 | https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=38987 Document doc = Jsoup.parse("<template><isindex action>"); assertNotNull(doc); assertEquals("<template><form><hr><label>This is a searchable index. Enter search keywords: <input name=\"isindex\"></label><hr></form></template>", TextUtil.stripNewlines(doc.head().html())); } + + @Test void trimNormalizeElementNamesInBuilder() { + // https://github.com/jhy/jsoup/issues/1637 | https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=38983 + // This is interesting - in TB state, the element name was "template\u001E", so no name checks matched. Then, + // when the Element is created, the name got normalized to "template" and so looked like there should be a + // template on the stack during resetInsertionMode for the select. + // The issue was that the normalization in Tag.valueOf did a trim which the Token.Tag did not + Document doc = Jsoup.parse("<template\u001E<select<input<"); + assertNotNull(doc); + assertEquals("<template><select></select><input>&lt;</template>", + TextUtil.stripNewlines(doc.head().html())); + } } From 3e17112a353c6483c2f2fb9485d1d120b4203cd2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 11:22:51 +1000 Subject: [PATCH 641/774] In deeply nested template at EOF, stop processing if can't break out of template state #1637 --- .../org/jsoup/parser/HtmlTreeBuilderState.java | 4 +++- src/test/resources/fuzztests/1637.html | Bin 0 -> 24053 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1637.html diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index fe8888379d..9efa416974 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1549,7 +1549,9 @@ else if (name.equals("col")) { tb.clearFormattingElementsToLastMarker(); tb.popTemplateMode(); tb.resetInsertionMode(); - return tb.process(t); + if (tb.state() != InTemplate) // spec deviation - if we did not break out of Template, stop processing + return tb.process(t); + else return true; } return true; } diff --git a/src/test/resources/fuzztests/1637.html b/src/test/resources/fuzztests/1637.html new file mode 100644 index 0000000000000000000000000000000000000000..ad1a183f730dff224a02bd2cde4e2bf267ff0994 GIT binary patch literal 24053 zcmeHPOLBrh5H<T;B35OY3#2G6kz4?iait|ClZ9u=adN8cf<jLt(}OcG0-EQl#ZiXt z_g;Tc^*+(rYNHap-tBav)i&Q`x_l}cFY@7zcStWeK3rPr+)1p(8rP5b1OMPZ^bh)v z{C6S0_sRd+s(7{2IBEc~z=~DY1bq%_Ak@Hi5AgjXi$Na^0RP*1*M4q$XuKof|NrKH zP_j5JHt0<oC{6h&Tb<*C@Dt0DRDTYiwYI*rO1JrOn6X?glYt4h^VEKqd4JLw9zhP$ z7|==9`)sQUrIV-6-N*YOS4I9<9EviSi|6V^*Q_i2(rkp3lQfYP?1T|5rTzEfhi8|I zY)1qi0aaAO*cgK<;6a@r{8>V%_I`(pFz(bePYm#`Y73e6n)D%@8Mk{GX3?yQv_6^T zX45DI&RZZjW|9T@a0?~5I?-(TtQOax`aty|5ir8BkSd&z4EZ?Au2vLKeVqBHPl_d| z8Q%^y%==M&kZ)tq81g~hGyn@V%zBCk<OA|C7*$TuMe}m{Mq!HPMQk-%Zi#?HG%sjg ztQ;5TMg*#l!Thu$gX#m-$6!>U`k-RoA#|-Y>ye6?sB^me#=EL5$Oq&D@=>)I7C9VZ z5WWub;h~tg^WsdyK@4Sh&MEWsxi-u`F#GVA&LEP=i!uAa?1SYqs)gByOgVpkbD>Vn z8k!gOi2>`xhB-ZM+!Ejku0z@h$On1T04&rn>nR?P56H)0R5|;aXwA#RqIy6+Dqj<Q zOMf_>e}%9@K8y$EYnYv(`XCB4=)&F!v?i-Q&VG8Q@Ffrms)?UucrEH4$YKyOt3k+b zceF0`(9uefo7NJ@ha?s@vLGK_|6zEEYsP!V*(gQGCy<XLdPFavlWBMW)yKruhn*C> zj<Dp_*3D1YNV)K{P%i*9IPA!i43P=HoWPqH(VDD>zW2Jf`Zpf@Pg9htl?87Y;h9(y V@;r)1TooH%;UrccIeCk-(?1<Z13dr$ literal 0 HcmV?d00001 From f10e524a45c0690b9649240d86e6ba573fbdba0d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 11:26:34 +1000 Subject: [PATCH 642/774] Zip test resource --- src/test/resources/fuzztests/1637.html | Bin 24053 -> 0 bytes src/test/resources/fuzztests/1637.html.gz | Bin 0 -> 411 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/test/resources/fuzztests/1637.html create mode 100644 src/test/resources/fuzztests/1637.html.gz diff --git a/src/test/resources/fuzztests/1637.html b/src/test/resources/fuzztests/1637.html deleted file mode 100644 index ad1a183f730dff224a02bd2cde4e2bf267ff0994..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24053 zcmeHPOLBrh5H<T;B35OY3#2G6kz4?iait|ClZ9u=adN8cf<jLt(}OcG0-EQl#ZiXt z_g;Tc^*+(rYNHap-tBav)i&Q`x_l}cFY@7zcStWeK3rPr+)1p(8rP5b1OMPZ^bh)v z{C6S0_sRd+s(7{2IBEc~z=~DY1bq%_Ak@Hi5AgjXi$Na^0RP*1*M4q$XuKof|NrKH zP_j5JHt0<oC{6h&Tb<*C@Dt0DRDTYiwYI*rO1JrOn6X?glYt4h^VEKqd4JLw9zhP$ z7|==9`)sQUrIV-6-N*YOS4I9<9EviSi|6V^*Q_i2(rkp3lQfYP?1T|5rTzEfhi8|I zY)1qi0aaAO*cgK<;6a@r{8>V%_I`(pFz(bePYm#`Y73e6n)D%@8Mk{GX3?yQv_6^T zX45DI&RZZjW|9T@a0?~5I?-(TtQOax`aty|5ir8BkSd&z4EZ?Au2vLKeVqBHPl_d| z8Q%^y%==M&kZ)tq81g~hGyn@V%zBCk<OA|C7*$TuMe}m{Mq!HPMQk-%Zi#?HG%sjg ztQ;5TMg*#l!Thu$gX#m-$6!>U`k-RoA#|-Y>ye6?sB^me#=EL5$Oq&D@=>)I7C9VZ z5WWub;h~tg^WsdyK@4Sh&MEWsxi-u`F#GVA&LEP=i!uAa?1SYqs)gByOgVpkbD>Vn z8k!gOi2>`xhB-ZM+!Ejku0z@h$On1T04&rn>nR?P56H)0R5|;aXwA#RqIy6+Dqj<Q zOMf_>e}%9@K8y$EYnYv(`XCB4=)&F!v?i-Q&VG8Q@Ffrms)?UucrEH4$YKyOt3k+b zceF0`(9uefo7NJ@ha?s@vLGK_|6zEEYsP!V*(gQGCy<XLdPFavlWBMW)yKruhn*C> zj<Dp_*3D1YNV)K{P%i*9IPA!i43P=HoWPqH(VDD>zW2Jf`Zpf@Pg9htl?87Y;h9(y V@;r)1TooH%;UrccIeCk-(?1<Z13dr$ diff --git a/src/test/resources/fuzztests/1637.html.gz b/src/test/resources/fuzztests/1637.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..5f7a691058e15fbf72399c4daa8d5d00bc307749 GIT binary patch literal 411 zcmb2|=HMv4@1DrOZD?j}u9s1go5S#Sj(6T60}<DkduC^-wB2Rcbi-wL<Aqs*8iIAQ z8^op^UNvK?kV8m~*Hnu-et9?djZL+aUr+XN5c4~A@{IM&u&EXwKUaly?PpqF-*caR zt=~I|mOoF_?SG#1ulwYFBHr(i#`kQ8x=+!k_eVXSYybVX|G$_||K<Bk>@2PJxzy^t z%lWtdE>m5fxa-5Hx?1+vORQ?ckNb*0y~D2m?&;=V74g^XuAEPl+;+G*(OAOx&DIRh z6Q}>CZwM7}*dN_mD==SfDRcVg&r7bdTdi)kyTJa-fPn^#$szLo&CkH*Z-|(?<d?zU zZy#STFW<p3{mIKoym~L2e99SWZ!pch>L~w$xmKcK$<>7s)eElmhrDLKGtWZk<AW^T n>3=W%k?v1Dz2SVY{&%)7`(qcse6n`7XbrQSZdmHqSOx|F$dbdt literal 0 HcmV?d00001 From 6607830962b35cbf6be8a8bd8199745dbd21b067 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 15:59:18 +1000 Subject: [PATCH 643/774] Speed optimization when reparenting children into an empty element Fixes #1638 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Node.java | 5 ++++- .../org/jsoup/parser/HtmlTreeBuilderState.java | 5 +---- src/test/resources/fuzztests/1638.html.gz | Bin 0 -> 1832 bytes 4 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/fuzztests/1638.html.gz diff --git a/CHANGES b/CHANGES index 9ada384bdb..04b67d7c39 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,10 @@ jsoup changelog document text that they are matching against. <https://github.com/jhy/jsoup/issues/876> + * Improvement: in Element, speed optimized adopting all of an element's child nodes into a currently empty element. + Improves the HTML adoption agency algorithm when adopting elements with many children. + <https://github.com/jhy/jsoup/issues/1638> + * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 9fe7417159..6d6234a786 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -520,13 +520,15 @@ protected void addChildren(int index, Node... children) { } } if (sameList) { // moving, so OK to empty firstParent and short-circuit + boolean wasEmpty = childNodeSize() == 0; firstParent.empty(); nodes.addAll(index, Arrays.asList(children)); i = children.length; while (i-- > 0) { children[i].parentNode = this; } - reindexChildren(index); + if (!(wasEmpty && children[0].siblingIndex == 0)) // skip reindexing if we just moved + reindexChildren(index); return; } } @@ -544,6 +546,7 @@ protected void reparentChild(Node child) { } private void reindexChildren(int start) { + if (childNodeSize() == 0) return; final List<Node> childNodes = ensureChildNodes(); for (int i = start; i < childNodes.size(); i++) { diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 9efa416974..1cbe73cd73 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -918,10 +918,7 @@ else if (!tb.onStack(formatEl)) { Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); adopter.attributes().addAll(formatEl.attributes()); - Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); - for (Node childNode : childNodes) { - adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. - } + adopter.appendChildren(furthestBlock.childNodes()); furthestBlock.appendChild(adopter); tb.removeFromActiveFormattingElements(formatEl); // insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. diff --git a/src/test/resources/fuzztests/1638.html.gz b/src/test/resources/fuzztests/1638.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..005a8f861b50bba37523b5e4878faacb71a36dfc GIT binary patch literal 1832 zcmb2|=HQtA*gcVf+tAF|LNB8vH;3WvZO6Q6r7|oD{7XJE#anc8&+eGkdNrdny8qFw zweFvp4zazy@$Bz1zI=mP38x<!Gk*SkcvDc%e7)ZL{qgtW_Wgah{JsA4+4l9nzkV;D zA0J|7+W-H{{GHX0xaa>re(&Pu`Q~3g9M;d@w{w4N%y*;szZ?Iv#KnJ{G1+ft?cZm+ zZ~yM!bmPywaQS`lvHuk7L(1d6dY`ZUewbh9TjIo{hmX%G;p*0rvwOBAI3nfa2SfF_ zhm^Y8`z>FJh(+4eobg<~;l!hd$$nO?u44T1#g{a7HdXup8vXN$j!-1e8LQ4S8zT75 zSVfXe{a8M|F5y~da@*0Ogli|028cRVZ~THiZLURHgbt9>8BWx>%|Ct|dG(Ut{=tps zM+^77nm+IQyLS1AH;0LdX1pn6ivKyKzU#o)FC;tmDNH=0+DAj4{Gw+x^^l!<=A?hz zSX1`pt>oPI{y%@MT3z?=<Fj{>fB)aAe&zpgwe~*S{I@69|NHyBqP+g@$^VP3^Y>MJ Te>^>3zn)RblDY1h3ljqXu!vz6 literal 0 HcmV?d00001 From 803be570a0f6dd939ad12b6c28c34d35ee410cbc Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 16:28:23 +1000 Subject: [PATCH 644/774] Don't transition into InSelect if if was an empty tag Fixes #1639 --- CHANGES | 3 +++ .../org/jsoup/parser/HtmlTreeBuilderState.java | 1 + src/test/resources/fuzztests/1639.html.gz | Bin 0 -> 342 bytes 3 files changed, 4 insertions(+) create mode 100644 src/test/resources/fuzztests/1639.html.gz diff --git a/CHANGES b/CHANGES index 04b67d7c39..207db684ff 100644 --- a/CHANGES +++ b/CHANGES @@ -41,6 +41,9 @@ jsoup changelog available in the DOM, and will be normalized if possible on output. <https://github.com/jhy/jsoup/issues/1474> + * Bugfix [Fuzz]: fixed an IOOB when an empty select tag was followed by a body tag that needed reparenting. + <https://github.com/jhy/jsoup/issues/1639> + * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods. <https://github.com/jhy/jsoup/issues/1628> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 1cbe73cd73..516782b913 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -544,6 +544,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.reconstructFormattingElements(); tb.insert(startTag); tb.framesetOk(false); + if (startTag.selfClosing) break; // don't change states if not added to the stack HtmlTreeBuilderState state = tb.state(); if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell)) diff --git a/src/test/resources/fuzztests/1639.html.gz b/src/test/resources/fuzztests/1639.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..6183efa55c48bc2ce56a3c9fc846610e1894f577 GIT binary patch literal 342 zcmV-c0jd5UiwFqqBS>KY3o$k`IWA~)ZEOIuNwq3U%}7m5u_;MR@=0X`5q2PUPO5@F zkYS^5lbcwSZD$9Ohfs+qJi^poFjZ1nkXm94G8x2$;$nz32n9wE1qT}ZKQCwiv4Pko z1py&cfgKQmunm}-n^=LYhD?})RrCrGI+2(R2=>twTYQ=-;M(?}OBEMl)xjW)%2!4U z7y||d225>7aXP^`#e;y)h|-HDm6xAl0`!|rMTIWVkC+fc1CnA@OyvnEJRAMA{GwbN zMLQ@1H>n2Kg2fRi933FO#Ejs({x1O~%Hq_V)MO|NoJe7r!e+*-d9%fAlJZk3ZEQ+y zR2hNdz?=e>hml1aKrARld`=-u6;aVJ1QVF!=#l5ZfdjS_M&qP1^Ya`W(h6;W2rQCh oQ({+@h)p?EFg3|82PTNe>|X405Ec;Ilw_6w0KByyMim7B0Dr%UH~;_u literal 0 HcmV?d00001 From cfa5c1d0ffbf9b1b5ff749951a2d62c7ad0c09e4 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 20 Sep 2021 16:56:46 +1000 Subject: [PATCH 645/774] Speed improvement for very deeply nested stacks and fake closing tags Limit the search depth to 256, like in the HTML parser. Otherwise, performance would get bogged down when a huge (thousands) deep stack was created and then followed by many fake closing tags. Fixes #1640 --- .../java/org/jsoup/parser/XmlTreeBuilder.java | 8 +++++++- src/test/resources/fuzztests/1640.html.gz | Bin 0 -> 6189 bytes 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1640.html.gz diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 1c49f146ee..5fad99e2c7 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -130,10 +130,14 @@ void insert(Token.Doctype d) { * @param endTag tag to close */ private void popStackToClose(Token.EndTag endTag) { + // like in HtmlTreeBuilder - don't scan up forever for very (artificially) deeply nested stacks String elName = settings.normalizeTag(endTag.tagName); Element firstFound = null; - for (int pos = stack.size() -1; pos >= 0; pos--) { + final int bottom = stack.size() - 1; + final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; + + for (int pos = stack.size() -1; pos >= upper; pos--) { Element next = stack.get(pos); if (next.nodeName().equals(elName)) { firstFound = next; @@ -150,6 +154,8 @@ private void popStackToClose(Token.EndTag endTag) { break; } } + private static final int maxQueueDepth = 256; // an arbitrary tension point between real XML and crafted pain + List<Node> parseFragment(String inputFragment, String baseUri, Parser parser) { diff --git a/src/test/resources/fuzztests/1640.html.gz b/src/test/resources/fuzztests/1640.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..a9ce0f644655f1fc5d83fdfa91b0a101f3e365c0 GIT binary patch literal 6189 zcmd5=d0f)z*4OH4t7fJxZl$HUq?wlFf>Tr33N@ybCN^0vX(G8{AT%vwK`wKxG1HML zH8K@7%_UGWMQ@s@;8H25n3@}ipkfJJmb%^djx{y!+<D*6?;oD$_u==P=bZ03=X<^e zxcT!zW_!L@T4cS|c9W%l*x4YZQ6|_wH4<G5&z$$_u{Atz^mJ<X7SHs3ZN8H3Xfnt! zDps9|W$W%cmVe4TuyWnP$)J?{Q<Ybs=Qi9(J6~V0VB@wMlT8zx^><<>A2#jywWPlV z8Mi1q+IOA9!%RPMKz|eqAH~8N#yX})0rBlqcFqm|czExHDY5*WdnAF+$js!>_7$MV zJSw8jozD~wwFE)yB^Yr%*kDOn_0APPRzLxc@LWj*lFLE%4eBsyJ!SC`ZAoh0LykK< zb{*BFYC(>}sai3R%hbcNAJP><w%xa#nq@zScl*a8kWUOX;1KFgeXE;>whNq^qLoDG z0kHD?FYC#c(tq%%8nDv=Ny%*R-}a<mp)R-`_O6<O5FTc{@kR^BdM7LGa*2q2yN;%= z;n{i|P5pJRdD?gKPifU&B{V-}&pYVByMfIIV9kku-Hx%^+9v*VEa8a4B5QU)Q9fXM zqPt_Jp+y`UYaXWomLF2YHr4tbzFcbXb;Rn87NDjKEpXO+TzV-R>r>4uvP^4s*^{kU zn-fdfV~PV1r0Tu^0H{^tYP)Sl_-Y6FC#A1(Myxuzr46-Eybr4<J%3blYD(>Ki<XPj zF$<P`!HEq2@8K7Xb7l9GIaSvvZTBLarfmS#)$A#IgY<-zf7#^~rg-6nyQp5hhviNp zZ~kW4=z90m{Jo8We5s$MPHKl*OI;n~H0VSP)Q*gm?S(E*_%9f3Qw@t;4y(;5KWnOg zL=kuVK3zId??r|@CeT%DW{tUt-kpxJ?Wuw}xNl*e@A1H^Q~D!t_+!DLS<-MDQ^NS{ zv!t`8?;&eGH{;JhtngX;8sK9Epoxe;>L&~wT%|YWCJhhF{`1Hm)}-7o4Rm}U{~$zV z&)ss{p*hQ2lJ$$r0Nd-2e#oxh1HTU?YvJ{|DS~XQ!M3L>)SVy9jXwILl`~2Rf7hMj z4v}RbDB-us_6g&E7y8$uFLSp52aB&^CiVvWzs=>C8u;ScIS0-?!Yl`hBf$Sws>s`4 zC9P%Lp7I~STrQG9Sv{8TOo$$Anc56sP1mY&!XvS)V997OcdWWsoz#aP5xgaM9SXPF zK-B`@|4^C`v3cVydW50*)q$(&_OZVysy8U`P`ZZHJ;s2Gl$%d7i2x~XWMLTHF%W** zOq#Yu%C`DS{nqJ5OY;Ku4BJM0sOl?R$Tu^+e6b}1$UUIPt!!~${C+QZ*k4+R|0p(Q z>j@LrypMEl(O0w~^SSGqp(m*eQUnLov!&(CoQN3JlAZP<k>yJEg}2N0fVH!gi(mY7 zQBTgtCAiCrku0NW4j}GGAM0gyUfN4U&(K66B#9Oz=+bo(iRsDX%*;!R7F+6U4tOA( ziGTmEuW4OGa3|)#@ao0~@{r$D{=2`Zw&>vd{-y=Xm1UF~lAbu@^D45v&D|=m!qD-a zqBgKD3l|g9JIb0v79_<=FQi7CkJDWD6|Lo&H($rrX5H8Jq{l!wrv6%>WA?u)ir%y* zXv2LQr=U>y#h(j4^j==XwR%QK>Vr=~uNLj9wNl<rCF~xTgXx&*o~rl`)vlt)@-JMh z{xk9MInGeac#PO>ur~L7$(4em%3RTxN0-U?jGN0EC?laQU_?$sq_3^w5a#>s9zGv3 zTZm(nlGu*$R*>eeS-mttKFC8xdt@O3B>>Xes5Wos%gYzcI4;qP{7mI)cEoL^gL;wo zV#4Y2UTZDQ-iyD-NoYqT+NO_M5{xn$mdJ_Kf085GQVR1hSub(2I1_P5!7@;0@I9o6 z6Nz$)fjE6(R(h2hb~$)J1x{aNp(bs?e_9{&Fea)4Wo=1DCgjevR5z0ns~^$doyl73 zvL{7nR=SqDmSeS6U><?{$d$hE7V|I*?v!k#rhoRbv(=5y;o|>}*_n7>626gj4Xd0| zG=U#TWWR=6nsHXge=HML1?<ZQ&)<u2lQVPjBv!_KM)q<|p@pS^vOT=d>>>XeFSyp0 z-9zmD4L4uKE^)vb8J9m2b=Ms?89iIiw79QG9^g$thL=PBUMZ;iEulF%c{%3Lbm7RH zkHn}jzW0`4O$Oq~M<!gACP-s%!I5fY^TIY2#hg&Y&cW`21(Hav6(k7RJx3RHsNzy) zQ01DFQpWr#IDgIEur}4AD;v<%;O39R0eHPyK)YJdck&F%`vl`njB6V0Tmw4)162k& z1%?0L8pKX)hoO<-6F2k9XE=Qj_x3i+DR6FXE|eqDF!E3Ft-LCoU2ZDVe|M08>yC<e z(5?1V5JB=h(7a@;T36n3osv4&T3Xal`XVYuBI=bC7aJTmErwb^EsE9f^|_AW!DBt$ z>EpdAI+zI2J1Wi-Y#A|fU#E{W{yLGN>i0`n)`L*i%E@hnQkHdN^0SFp?OQ?i9n=V( zM>B>L&4-CONf?K=dJ8|m2!Jq`)Q-${sPUVLh%!$`+xkEw0#cJ!<(2esd{{HjNIxtM z#wi|-3a(t|RKrr6N=fR=deinf(T(W0>331OWpHj=VN$lo4ojNx3WDeEUe{wzJn#xs z{jJvz!#f*vc&0|g$ze_1kc%h4cu7qz*9un7a(dA2tsk$(Sv(UcA%%xh8bgqeJ(&gd zGgVEEX_W`bhl{Xz7kXRncdqYLKf_cHQGdsosJo<PE{aFJ7^nhL7PJg8yE7*GgPZO$ zpX?gG8|ztw16D<W4;>hv3Inc;y4F-1f<JgJ!`hN`e0gLsYjBiP?j?xOU!G#sWw8}+ zmDbf|g~iM;$CamtslL-)R$>@GPt~N34|PUCDuOKyrb|(f&68F~pj1hhkdQm&K#fEX zqa{sKxJb0{e3WZcg>Wygm51I0IKgXGhG{eTROl2QGcj^l(g(qdXBc3h==Ior5%{P< zDlf6Y1ab&Xt8A8(p!nXB$#nrd9Q)Oky8uW~kU%pSjT}QuthU-az`9<NNbe@&i4|ef znPU7+KV33Y7bSqi7znz87SlQr0s~bp;jLt>rQ55(({Y0nN3bvM<Wv`qX)yDQqfQ|s zr`p;p$~@EBJ3OE)mRUsn@u1P}%%d;O+*JYxW@_-nuDAS#VLV1WT{k{H-6s->KydV1 z*Q%#e9A}8{YSe%)Cck3|$2cd4QZx>lE=KT&>f*6oq*f9nO_FOzdDOM-?SgNUo)qrq z^#4@QTJ*XG*lki92N0GzMvLk?ke)I4X`w&&rpvotaqLw01x!fkPCG6+)DPyHO+FWO z78sS>&?DMxGrIpxQgUJ*S3MAwEZ+aB7q)Ze6>O*Q7OghyTb+y%M?%+<nc@u652Q*% zDdM(sH^xO}5S+*T7#FqW2;=!Nlp^`NQFT|dC;S-3ularqGg{V1u*|$HT08LE=bkHT zu9DM8sNK;Q3bb?w=Wnt}otj5o=j<px@p}CZt{P_Rv!7~8M;>}Bixwhgbej(93$zkS z^bi=pDZ8al&o(O_=81yHOF64f8Ur{+9nDsE;)80P=nRADLc{Gi#>RABmax$90x8M} zc)GgvZK0v&xb%(Iz!A<ej_M<hYH3WgIOiV;q5akjQ?Bv_{mC*Ni;>_E`uR{h|K{NC zyW!0>8(;0h_mE9C7*8YZ4X%@SzVRt-G~PyEnKYrAx{xwQ-&r**Q8oH+ug|IiuZG%% z*o}$D=CH2lh?bpSVHw^?1f-J+$Di(N?iiI$DmN=hMg|gLay;Hi@-kjBE}RZ6z4m;@ zS0f*{WwONIEu}NR(fhj2^~iI7ulR=MbC30m2a1y}CP%JST#hn1^%M7UX?a5slGH8k z7q?v$oxPayl3biD67Nr$y6p$;=Vz#jVh(6JGj>ey^+zBTErRRwW1YFRjr~Hh0ReR; zj(cFTMK^^?yEI1Vf73~#@O?4Q3z28y8X7XsEvayax|J<!j7~?rbLeyXg+!;kC}aDH z7?(O~=$?Ia2gvbZSJ=-cZMZxMx<JhI98keSDh6Ry`$cS+#g_4%HrYL3TbrIUY0a7q z7R=y5*9O~)0{t1xCSI-`7v`1BKbahS?h!1Sc*BD-&yHjSj1;~Y6yTVC8~MlTQEi)_ zZ@cPp%LS@ElyVFh_)=irOnL4^Ey<s$@7xxxv-$MEHc{{<E-am-Q>v4}RXJu;eV_@6 z^U)8;p^)?bt;q504l8r|V}&;~Aj7b`w-Xw>cDlhqeU?4wL&N9`$QJ{!yQ|O99DVKk zoRpfXfRUH3FqeQlU>p^W$|4&@rK;*x-wibsaK`)+bpx)~zuh1GKO>zr6$8OeB$v-B z{2BNW`SWc?Ao~^k(ecRf>@vz@`}lbu8uDi}wJ)LSucB%Pe<|;DKu)yU=vW;uM^6sX zlVimx>4YwCduf*s3;Ch)^tZ+bweS)R`qv9%UpxV({K_T;24*KgyfyS)|C?IlVqS+H zwZl3<s!*xADHKb>vY4Vk)vZ|{MyJ`-O4?qRHxa>0P0F9Jn>v1eKTSzRy59(yU*hz? zNK^d+QxfyGqon$RTvg_^VLv18I=ZC}tjkN0lGwnUTb~de+fC4i;q8bcADG%s<TWjm zAi9lHj$-O~1UVq}?f2mGVBT%1(kY!pgt~u@Jog_O*Ovx4ERju7Uia|!aZRME(mw$e CF#Asc literal 0 HcmV?d00001 From 4940b036720ca7d7ea1f61b933d5ebfb2ddcb6c4 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 20 Sep 2021 21:29:54 +1000 Subject: [PATCH 646/774] Refactored reset insertion mode to a switch from an if/else ladder Will make for a few less string equals hits, particularly on big stacks. --- .../org/jsoup/parser/HtmlTreeBuilder.java | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 23529d53b4..429f4ccf6c 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -463,7 +463,7 @@ void resetInsertionMode() { transition(HtmlTreeBuilderState.InBody); } - for (int pos = bottom; pos >= upper; pos--) { + LOOP: for (int pos = bottom; pos >= upper; pos--) { Element node = stack.get(pos); if (pos == 0) { last = true; @@ -471,46 +471,57 @@ void resetInsertionMode() { node = contextElement; } String name = node != null ? node.normalName() : ""; - if ("select".equals(name)) { - transition(HtmlTreeBuilderState.InSelect); - // todo - should loop up (with some limit) and check for table or template hits - break; - } else if ((("td".equals(name) || "th".equals(name)) && !last)) { - transition(HtmlTreeBuilderState.InCell); - break; - } else if ("tr".equals(name)) { - transition(HtmlTreeBuilderState.InRow); - break; - } else if ("tbody".equals(name) || "thead".equals(name) || "tfoot".equals(name)) { - transition(HtmlTreeBuilderState.InTableBody); - break; - } else if ("caption".equals(name)) { - transition(HtmlTreeBuilderState.InCaption); - break; - } else if ("colgroup".equals(name)) { - transition(HtmlTreeBuilderState.InColumnGroup); - break; - } else if ("table".equals(name)) { - transition(HtmlTreeBuilderState.InTable); - break; - } else if ("template".equals(name)) { - HtmlTreeBuilderState tmplState = currentTemplateMode(); - Validate.notNull(tmplState, "Bug: no template insertion mode on stack!"); - transition(tmplState); - break; - } else if ("head".equals(name) && !last) { - transition(HtmlTreeBuilderState.InHead); - break; - } else if ("body".equals(name)) { - transition(HtmlTreeBuilderState.InBody); - break; - } else if ("frameset".equals(name)) { - transition(HtmlTreeBuilderState.InFrameset); - break; - } else if ("html".equals(name)) { - transition(headElement == null ? HtmlTreeBuilderState.BeforeHead : HtmlTreeBuilderState.AfterHead); - break; - } else if (last) { + switch (name) { + case "select": + transition(HtmlTreeBuilderState.InSelect); + // todo - should loop up (with some limit) and check for table or template hits + break LOOP; + case "td": + case "th": + if (!last) { + transition(HtmlTreeBuilderState.InCell); + break LOOP; + } + break; + case "tr": + transition(HtmlTreeBuilderState.InRow); + break LOOP; + case "tbody": + case "thead": + case "tfoot": + transition(HtmlTreeBuilderState.InTableBody); + break LOOP; + case "caption": + transition(HtmlTreeBuilderState.InCaption); + break LOOP; + case "colgroup": + transition(HtmlTreeBuilderState.InColumnGroup); + break LOOP; + case "table": + transition(HtmlTreeBuilderState.InTable); + break LOOP; + case "template": + HtmlTreeBuilderState tmplState = currentTemplateMode(); + Validate.notNull(tmplState, "Bug: no template insertion mode on stack!"); + transition(tmplState); + break LOOP; + case "head": + if (!last) { + transition(HtmlTreeBuilderState.InHead); + break LOOP; + } + break; + case "body": + transition(HtmlTreeBuilderState.InBody); + break LOOP; + case "frameset": + transition(HtmlTreeBuilderState.InFrameset); + break LOOP; + case "html": + transition(headElement == null ? HtmlTreeBuilderState.BeforeHead : HtmlTreeBuilderState.AfterHead); + break LOOP; + } + if (last) { transition(HtmlTreeBuilderState.InBody); break; } From aaafac1ad0faf9aa63691e85b114b4f09f279c06 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 21 Sep 2021 14:16:51 +1000 Subject: [PATCH 647/774] Refactored fragment context state to use a switch --- .../org/jsoup/parser/HtmlTreeBuilder.java | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 429f4ccf6c..1b382bb654 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -103,24 +103,37 @@ List<Node> parseFragment(String inputFragment, @Nullable Element context, String // initialise the tokeniser state: String contextTag = context.normalName(); - if (StringUtil.in(contextTag, "title", "textarea")) - tokeniser.transition(TokeniserState.Rcdata); - else if (StringUtil.in(contextTag, "iframe", "noembed", "noframes", "style", "xmp")) - tokeniser.transition(TokeniserState.Rawtext); - else if (contextTag.equals("script")) - tokeniser.transition(TokeniserState.ScriptData); - else if (contextTag.equals(("noscript"))) - tokeniser.transition(TokeniserState.Data); // if scripting enabled, rawtext - else if (contextTag.equals("plaintext")) - tokeniser.transition(TokeniserState.PLAINTEXT); - else - tokeniser.transition(TokeniserState.Data); // default - + switch (contextTag) { + case "title": + case "textarea": + tokeniser.transition(TokeniserState.Rcdata); + break; + case "iframe": + case "noembed": + case "noframes": + case "style": + case "xml": + tokeniser.transition(TokeniserState.Rawtext); + break; + case "script": + tokeniser.transition(TokeniserState.ScriptData); + break; + case "noscript": + tokeniser.transition(TokeniserState.Data); // if scripting enabled, rawtext + break; + case "plaintext": + tokeniser.transition(TokeniserState.PLAINTEXT); + break; + case "template": + tokeniser.transition(TokeniserState.Data); + pushTemplateMode(HtmlTreeBuilderState.InTemplate); + break; + default: + tokeniser.transition(TokeniserState.Data); + } root = new Element(Tag.valueOf(contextTag, settings), baseUri); doc.appendChild(root); stack.add(root); - if (contextTag.equals("template")) - pushTemplateMode(HtmlTreeBuilderState.InTemplate); resetInsertionMode(); // setup form element to nearest form on context (up ancestor chain). ensures form controls are associated From cc2363e4501e086b6ba628ececb7716cfad87796 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 21 Sep 2021 14:26:54 +1000 Subject: [PATCH 648/774] Scan ancestor chain with less GC --- src/main/java/org/jsoup/nodes/Element.java | 2 +- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 230790d4ea..fb295208ef 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -258,7 +258,7 @@ public Map<String, String> dataset() { return attributes().dataset(); } - @Override + @Override @Nullable public final Element parent() { return (Element) parentNode; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 1b382bb654..2748e3c4a6 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -138,13 +138,13 @@ List<Node> parseFragment(String inputFragment, @Nullable Element context, String // setup form element to nearest form on context (up ancestor chain). ensures form controls are associated // with form correctly - Elements contextChain = context.parents(); - contextChain.add(0, context); - for (Element parent: contextChain) { - if (parent instanceof FormElement) { - formElement = (FormElement) parent; + Element formSearch = context; + while (formSearch != null) { + if (formSearch instanceof FormElement) { + formElement = (FormElement) formSearch; break; } + formSearch = formSearch.parent(); } } From 4ec4ace6dd3c2285070f190229d91cf6a0a35754 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 21 Sep 2021 16:24:19 +1000 Subject: [PATCH 649/774] Confirm to CodeQL that we use an identity hashcode. --- src/main/java/org/jsoup/nodes/Node.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 6d6234a786..5d62478408 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -699,6 +699,17 @@ public boolean equals(@Nullable Object o) { return this == o; } + /** + Provides a hashCode for this Node, based on it's object identity. Changes to the Node's content will not impact the + result. + @return an object identity based hashcode for this Node + */ + @Override + public int hashCode() { + // implemented so that javadoc and scanners are clear this is an identity test + return super.hashCode(); + } + /** * Check if this node is has the same content as another node. A node is considered the same if its name, attributes and content match the * other node; particularly its position in the tree does not influence its similarity. From ea7e7f0693642f14d0f4469d46a1ff4c9efe89c6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 21 Sep 2021 20:14:14 +1000 Subject: [PATCH 650/774] Adds grammar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76d88556ab..7fdd93eab9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ for (Element headline : newsHeadlines) { [Online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g), [full source](https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/examples/Wikipedia.java). ## Open source -jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available at [GitHub](https://github.com/jhy/jsoup). +jsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available on [GitHub](https://github.com/jhy/jsoup). ## Getting started 1. [Download](https://jsoup.org/download) the latest jsoup jar (or add it to your Maven/Gradle build) From 2b22ef803a5761dd960fbb7cbd3dd1d0c9bae9ef Mon Sep 17 00:00:00 2001 From: offa <bm-dev@yandex.com> Date: Thu, 23 Sep 2021 07:56:53 +0200 Subject: [PATCH 651/774] Add JDK17 CI build. (#1641) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7da03a0628..ec9c652c45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time - java: [8, 11, 16] + java: [8, 11, 16, 17] fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: From 011e83f495e1b7ab768985446abc9395c35f6e5e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 24 Sep 2021 16:12:11 +1000 Subject: [PATCH 652/774] Reset insertion mode correctly in very deeply stacks Fixes #1642 --- .../java/org/jsoup/parser/HtmlTreeBuilder.java | 2 +- src/test/resources/fuzztests/1642.html.gz | Bin 0 -> 180 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/1642.html.gz diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 2748e3c4a6..8a06312b6d 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -478,7 +478,7 @@ void resetInsertionMode() { LOOP: for (int pos = bottom; pos >= upper; pos--) { Element node = stack.get(pos); - if (pos == 0) { + if (pos == upper) { last = true; if (fragmentParsing) node = contextElement; diff --git a/src/test/resources/fuzztests/1642.html.gz b/src/test/resources/fuzztests/1642.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e63d8bef99e111303c9bd12309115cf710c150c5 GIT binary patch literal 180 zcmV;l089TLiwFqWUrk{E3o$k{GA?LzZEOHL&|p)Nn3R)hXH%SH<5*y4lY)Q;W+U^^ zVR0%F&!&`8SWUSRsPJs7O)(R8DQek4SV?I<nHuQ>aYk-_UQwz|acM3nj`I&R*c7Mc zq$ZcxlqBV+RN5rj6x)=f<`(26mZaL{W@M(k-@i9QQP(CpKPSB?zqG*SWUfAtt)FBA iM3pwbAe!=RQf*3#K#FX@0$_#>D1ZQ;QX%|=0ssK{d`^G> literal 0 HcmV?d00001 From 5f546b31307660fd260bb51c12a431c32c351c53 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 24 Sep 2021 16:29:43 +1000 Subject: [PATCH 653/774] Limit stack depth when cleaning up open templates at EOF Fixes #1643 --- .../org/jsoup/parser/HtmlTreeBuilderState.java | 4 +++- src/test/resources/fuzztests/39164.html | Bin 0 -> 24384 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/fuzztests/39164.html diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 516782b913..0bf7538209 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -1547,7 +1547,9 @@ else if (name.equals("col")) { tb.clearFormattingElementsToLastMarker(); tb.popTemplateMode(); tb.resetInsertionMode(); - if (tb.state() != InTemplate) // spec deviation - if we did not break out of Template, stop processing + // spec deviation - if we did not break out of Template, stop processing, and don't worry about cleaning up ultra-deep template stacks + // limited depth because this can recurse and will blow stack if too deep + if (tb.state() != InTemplate && tb.templateModeSize() < 12) return tb.process(t); else return true; } diff --git a/src/test/resources/fuzztests/39164.html b/src/test/resources/fuzztests/39164.html new file mode 100644 index 0000000000000000000000000000000000000000..1089955641d2121173440daac39bd24f70d0b08e GIT binary patch literal 24384 zcmeHP&5qMB5H?)6@&*g4R0)X))D^dE#U3~#AzmQ6b$_HJMJ0!x5LXU7z;fub@Kzkb zPP-&&#?3g1f7(nAtHx_L_WXSF%{XqDg&9x2#8JlOhr6q3sBB?&FU*u&_W6hsn#&pD z)OvD;c2&NAn$iGOGwm=*)mNVO1bd?fVPrUkH=xr&C*#rhUHJQZ8uJfR#-6RhF#P(0 z-K;;S4?lOe>vXm`iE;gC){@o)RwH1$pC$ExB{(&s!;xxTga$)13N>X>*$4*S-xr-a zm=rj)%e0DFnR7{d_=RVmQFelwb`U5a#Uui?rfBT&Y+Oj|7_clU(-OM(X8r^kJ^Pa- zJ0sEq<>?A+!ZZ_p^@b@Oks1g^>KX&#*V*Ei4(Vww%N_f#B^;)BFI7`Y5&-W_j&&pY zB)Q|3vt07zv-RYmW!i`UhoKybK7hA)C19t>8ZCI5@eSjjvTQq<#eB;CipnN?%GnKL z!J*3eEsHh_p2=EgE@b4^jswXP>YXj>5r4Tvf6?ME{^ANEMAh;=r%V5j*GaUEdAi7! z6LE=stkL#*CO=Nb`3Je573_B5n`yD5_2c%*ujTyJE=!92E)gGOaFAD}?`|>?z32RX zJzJ#<_M9)pDADN;U&_8<qi8>hH>FsUR$@(-Pqp&rZRM`pO|lPn>c{62*f0Ab=JH{9 z5wc<gsw7JQNB{_6hPqhO29!{iSc9^5qkzTGCJ6SuC<ngKmpu^)KU)&+Rb~v}hXXK_ zG^XX{yAL}pJ@`xZ3BS%3zlcLSz#QZL0t5o}CWybd(uQC8zjSgrayh_C9WXVMX=|Mr z@3&kp3vJrv`HE2_GOTeN1<<40Hemx$N!ynSu#2dx+s76FsXU_+6i>C{DNiVih2GsX z6rM^b(y}O`9HyoAo-<Sc7HR~>lAUR;OtU-k%|AdMRJy0qJwPikq?<sarQk5a&HD%x z@R4nqUUtLl!pcz7{0|C7XxnR0g4ORao;o#Kj$RM4vq$`*yvKnp821;DNY+CYonJ7+ z<4Xb9Uiwm3@8DbXf<mRY160teI9jqRm}SuU1q{8S{Huh6Kt!sQdbImo3VOp{=I<7O zU;M>iIcXvK!TJFwu(wr0=NB+18|eIk%o^Jm_oa*?05qpb5CALOyy1qZ`&s&?ai3~1 z8|{CSojn#22VZEYJDj#P6$6H2SN-H~#(@KjNOhYx^zKJ;$4X~fN83PLr+(Vou=%(a zhS8<J41zFNr}I@TexBog8Kny@TY|?k+49VGKSqmW&lauhN*qQn6(U{_i{cA?`~#7P Bg{=Sp literal 0 HcmV?d00001 From 88c19434c6ab8e39ab6c9ffd4854304db57fd911 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sat, 25 Sep 2021 13:00:26 +1000 Subject: [PATCH 654/774] Memoize containsIgnoreCase(seq) results and lower GC when scanning for </title Fixes #1644 --- CHANGES | 4 ++ .../org/jsoup/parser/CharacterReader.java | 27 +++++++++++-- src/main/java/org/jsoup/parser/Tokeniser.java | 9 +++++ .../java/org/jsoup/parser/TokeniserState.java | 2 +- .../org/jsoup/parser/CharacterReaderTest.java | 37 ++++++++++++++++++ src/test/resources/fuzztests/1644.html.gz | Bin 0 -> 810 bytes src/test/resources/fuzztests/39164.html | Bin 24384 -> 0 bytes src/test/resources/fuzztests/39164.html.gz | Bin 0 -> 669 bytes 8 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/fuzztests/1644.html.gz delete mode 100644 src/test/resources/fuzztests/39164.html create mode 100644 src/test/resources/fuzztests/39164.html.gz diff --git a/CHANGES b/CHANGES index 207db684ff..56d9f6e178 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,10 @@ jsoup changelog Improves the HTML adoption agency algorithm when adopting elements with many children. <https://github.com/jhy/jsoup/issues/1638> + * Improvement: increased the parse speed when in RCData (e.g. <title>) and unescaped <tag> tokens are found, by + memoizing the </title> scan and reducing GC. + <https://github.com/jhy/jsoup/issues/1644> + * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index af27c7aa72..605b19978e 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -104,7 +104,8 @@ private void bufferUp() { } catch (IOException e) { throw new UncheckedIOException(e); } - scanBufferForNewlines(); + scanBufferForNewlines(); // if enabled, we index newline positions for line number tracking + lastIcSeq = null; // cache for last containsIgnoreCase(seq) } /** @@ -656,11 +657,31 @@ boolean matchConsumeIgnoreCase(String seq) { } } + // we maintain a cache of the previously scanned sequence, and return that if applicable on repeated scans. + // that improves the situation where there is a sequence of <p<p<p<p<p<p<p...</title> and we're bashing on the <p + // looking for the </title>. Resets in bufferUp() + @Nullable private String lastIcSeq; // scan cache + private int lastIcIndex; // nearest found indexOf + + /** Used to check presence of </title>, </style> when we're in RCData and see a <xxx. Only finds consistent case. */ boolean containsIgnoreCase(String seq) { - // used to check presence of </title>, </style>. only finds consistent case. + if (seq.equals(lastIcSeq)) { + if (lastIcIndex == -1) return false; + if (lastIcIndex >= bufPos) return true; + } + lastIcSeq = seq; + String loScan = seq.toLowerCase(Locale.ENGLISH); + int lo = nextIndexOf(loScan); + if (lo > -1) { + lastIcIndex = bufPos + lo; return true; + } + String hiScan = seq.toUpperCase(Locale.ENGLISH); - return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1); + int hi = nextIndexOf(hiScan); + boolean found = hi > -1; + lastIcIndex = found ? bufPos + hi : -1; // we don't care about finding the nearest, just that buf contains + return found; } @Override diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index d8d9a7b9d3..1ebf0871d9 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -47,6 +47,7 @@ final class Tokeniser { Token.Doctype doctypePending = new Token.Doctype(); // doctype building up Token.Comment commentPending = new Token.Comment(); // comment building up private String lastStartTag; // the last start tag emitted, to test appropriate end tag + @Nullable private String lastStartCloseSeq; // "</" + lastStartTag, so we can quickly check for that in RCData Tokeniser(CharacterReader reader, ParseErrorList errors) { this.reader = reader; @@ -84,6 +85,7 @@ void emit(Token token) { if (token.type == Token.TokenType.StartTag) { Token.StartTag startTag = (Token.StartTag) token; lastStartTag = startTag.tagName; + lastStartCloseSeq = null; // only lazy inits } else if (token.type == Token.TokenType.EndTag) { Token.EndTag endTag = (Token.EndTag) token; if (endTag.hasAttributes()) @@ -274,6 +276,13 @@ String appropriateEndTagName() { return lastStartTag; // could be null } + /** Returns the closer sequence {@code </lastStart} */ + String appropriateEndTagSeq() { + if (lastStartCloseSeq == null) // reset on start tag emit + lastStartCloseSeq = "</" + lastStartTag; + return lastStartCloseSeq; + } + void error(TokeniserState state) { if (errors.canAddError()) errors.add(new ParseError(reader, "Unexpected character '%s' in input state [%s]", reader.current(), state)); diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index 2907ad59fc..acc8db35b3 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -186,7 +186,7 @@ void read(Tokeniser t, CharacterReader r) { if (r.matches('/')) { t.createTempBuffer(); t.advanceTransition(RCDATAEndTagOpen); - } else if (r.matchesAsciiAlpha() && t.appropriateEndTagName() != null && !r.containsIgnoreCase("</" + t.appropriateEndTagName())) { + } else if (r.matchesAsciiAlpha() && t.appropriateEndTagName() != null && !r.containsIgnoreCase(t.appropriateEndTagSeq())) { // diverge from spec: got a start tag, but there's no appropriate end tag (</title>), so rather than // consuming to EOF; break out here t.tagPending = t.createTagPending(false).name(t.appropriateEndTagName()); diff --git a/src/test/java/org/jsoup/parser/CharacterReaderTest.java b/src/test/java/org/jsoup/parser/CharacterReaderTest.java index a362b1e648..d9f2280177 100644 --- a/src/test/java/org/jsoup/parser/CharacterReaderTest.java +++ b/src/test/java/org/jsoup/parser/CharacterReaderTest.java @@ -220,6 +220,43 @@ public void matchesIgnoreCase() { assertFalse(r.containsIgnoreCase("one")); } + @Test void containsIgnoreCaseBuffer() { + String html = "<p><p><p></title><p></TITLE><p>" + BufferBuster("Foo Bar Qux ") + "<foo><bar></title>"; + CharacterReader r = new CharacterReader(html); + + assertTrue(r.containsIgnoreCase("</title>")); + assertFalse(r.containsIgnoreCase("</not>")); + assertFalse(r.containsIgnoreCase("</not>")); // cached, but we only test functionally here + assertTrue(r.containsIgnoreCase("</title>")); + r.consumeTo("</title>"); + assertTrue(r.containsIgnoreCase("</title>")); + r.consumeTo("<p>"); + assertTrue(r.matches("<p>")); + + assertTrue(r.containsIgnoreCase("</title>")); + assertTrue(r.containsIgnoreCase("</title>")); + assertFalse(r.containsIgnoreCase("</not>")); + assertFalse(r.containsIgnoreCase("</not>")); + + r.consumeTo("</TITLE>"); + r.consumeTo("<p>"); + assertTrue(r.matches("<p>")); + assertFalse(r.containsIgnoreCase("</title>")); // because we haven't buffered up yet, we don't know + r.consumeTo("<foo>"); + assertFalse(r.matches("<foo>")); // buffer underrun + r.consumeTo("<foo>"); + assertTrue(r.matches("<foo>")); // cross the buffer + assertTrue(r.containsIgnoreCase("</TITLE>")); + assertTrue(r.containsIgnoreCase("</title>")); + } + + static String BufferBuster(String content) { + StringBuilder builder = new StringBuilder(); + while (builder.length() < maxBufferLen) + builder.append(content); + return builder.toString(); + } + @Test public void matchesAny() { char[] scan = {' ', '\n', '\t'}; CharacterReader r = new CharacterReader("One\nTwo\tThree"); diff --git a/src/test/resources/fuzztests/1644.html.gz b/src/test/resources/fuzztests/1644.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e4bce4e269be107fcf3e9f60b56c498daf049cb0 GIT binary patch literal 810 zcmb2|=HU2U=$pvEZER_1ZmgG4lAFWu_Ub{-Lk1!Z4=2Vvh%MHrc*7yN;I4yt0ZY38 zi(rW|f9wH~y`AQnSwAKyP5l=zTYvq<^1o(B1!65PN$jjSJOAf$nc3HW{(e%r(m_Fh zgN3Q_f%?I}yY^KJaj-OfWxZTjAN6J1w4e7cPE|fOMZ9*?>&P%Ziw6zN5F)B5K<x1$ zChjyQc5M!XFm+g2>@o!h7^z8Yg*ux$?gN_Ae2c2?1NztTEi7!Q<;VqP)D1+Lf&goH z0-%l~r3xQd-qiob4T|f=@5}#w65O&Oe%a%9$G|BozWMmifbJ$$<747HQ5%4sIrbx7 mZ>Hh7Cc}I4nitOQ7MX4HWK+{c{{NQ8=4TW~n1udkWB>rcjT(9Y literal 0 HcmV?d00001 diff --git a/src/test/resources/fuzztests/39164.html b/src/test/resources/fuzztests/39164.html deleted file mode 100644 index 1089955641d2121173440daac39bd24f70d0b08e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24384 zcmeHP&5qMB5H?)6@&*g4R0)X))D^dE#U3~#AzmQ6b$_HJMJ0!x5LXU7z;fub@Kzkb zPP-&&#?3g1f7(nAtHx_L_WXSF%{XqDg&9x2#8JlOhr6q3sBB?&FU*u&_W6hsn#&pD z)OvD;c2&NAn$iGOGwm=*)mNVO1bd?fVPrUkH=xr&C*#rhUHJQZ8uJfR#-6RhF#P(0 z-K;;S4?lOe>vXm`iE;gC){@o)RwH1$pC$ExB{(&s!;xxTga$)13N>X>*$4*S-xr-a zm=rj)%e0DFnR7{d_=RVmQFelwb`U5a#Uui?rfBT&Y+Oj|7_clU(-OM(X8r^kJ^Pa- zJ0sEq<>?A+!ZZ_p^@b@Oks1g^>KX&#*V*Ei4(Vww%N_f#B^;)BFI7`Y5&-W_j&&pY zB)Q|3vt07zv-RYmW!i`UhoKybK7hA)C19t>8ZCI5@eSjjvTQq<#eB;CipnN?%GnKL z!J*3eEsHh_p2=EgE@b4^jswXP>YXj>5r4Tvf6?ME{^ANEMAh;=r%V5j*GaUEdAi7! z6LE=stkL#*CO=Nb`3Je573_B5n`yD5_2c%*ujTyJE=!92E)gGOaFAD}?`|>?z32RX zJzJ#<_M9)pDADN;U&_8<qi8>hH>FsUR$@(-Pqp&rZRM`pO|lPn>c{62*f0Ab=JH{9 z5wc<gsw7JQNB{_6hPqhO29!{iSc9^5qkzTGCJ6SuC<ngKmpu^)KU)&+Rb~v}hXXK_ zG^XX{yAL}pJ@`xZ3BS%3zlcLSz#QZL0t5o}CWybd(uQC8zjSgrayh_C9WXVMX=|Mr z@3&kp3vJrv`HE2_GOTeN1<<40Hemx$N!ynSu#2dx+s76FsXU_+6i>C{DNiVih2GsX z6rM^b(y}O`9HyoAo-<Sc7HR~>lAUR;OtU-k%|AdMRJy0qJwPikq?<sarQk5a&HD%x z@R4nqUUtLl!pcz7{0|C7XxnR0g4ORao;o#Kj$RM4vq$`*yvKnp821;DNY+CYonJ7+ z<4Xb9Uiwm3@8DbXf<mRY160teI9jqRm}SuU1q{8S{Huh6Kt!sQdbImo3VOp{=I<7O zU;M>iIcXvK!TJFwu(wr0=NB+18|eIk%o^Jm_oa*?05qpb5CALOyy1qZ`&s&?ai3~1 z8|{CSojn#22VZEYJDj#P6$6H2SN-H~#(@KjNOhYx^zKJ;$4X~fN83PLr+(Vou=%(a zhS8<J41zFNr}I@TexBog8Kny@TY|?k+49VGKSqmW&lauhN*qQn6(U{_i{cA?`~#7P Bg{=Sp diff --git a/src/test/resources/fuzztests/39164.html.gz b/src/test/resources/fuzztests/39164.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..50fd9f960a8531e8bac15316fc28412a18a53c1c GIT binary patch literal 669 zcmb2|=HR%S>zl~HZER_1W}=r-lAFWucJ@KP#|8pzXXAE!V&h&F!r9poDqt>rF!8)c zhxmyg^OaL}toU(3_|d6Ec?P~pv3p(0uUlHT9dY0)wJNKdzi55gW6AS9|5c2_V><sB zbx+mM|M#co$L^M`w`cie&V4fFM$MWD)2{Ey@;?7X`n%HO7kccG#+TOQ@4s&Ugx}mq zc)#*7Gxs^4SLc?Q*iA35@1DF-#^+C@>Dd=L{E|&s7fh3$=l7?-kXCH9_`RV{*ZADI z5FYy{hGLhin-)Dxo8$Op2FIgS*(_gApSAdWl<#`S*BV2vlfO1R?^Kw4_dJ7!ps-`i zO~pc`B|^e#=cdeRYxZ$@^D=ApM)@XLL$N>grz_V!@>B}>xo&fjrltI%SC(>f_uY6X zwQy<tii%Xzl(ozDUwdscFU?6!i?{Uk_g{No9#mMfZguyk>aYNj8v9wb7kAm-+~ME8 zyY*E1{oP;w1?2IiM!$$|S@_OYMEvnV549ZcB~?@2ebKnIX}#d9Z)y8y-p+n<C~ong zsNbRP@p(Vfx39PPkn=58ZCdfeI~{J>9ZVfe9!IprxD}2}{C=d$Z>>kQ^0_RfIa6+$ zVD;s>jEtaV%1<X0x$L<#bxLvbfmC14Nzx~K|9;6n;yhVqB1c8n)}r;Q$G1kPZJcpW zScKR7vCg5@)y$4DrM(O4c1os$JiBq8zMEpCeA{R4eJKo6Iyx2By%Crb?{Iao09sf) z{=;-me*wcHhDq8D0`B*OUt4ZfdfRKUy0aiC?Xp70YPKU24{dyRwj$7#ZCBv!d9{IR z){Y07H_G%Gsj7)*>Ier;dhm4SGLbiH&#mx1{!V{^tq}Jmsf#b<@AjovJH#_E006Lu BN*4eC literal 0 HcmV?d00001 From 41932fe2078f6c7c8414b0c71f65d6aa2d8d9a6d Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sat, 25 Sep 2021 18:38:33 +1000 Subject: [PATCH 655/774] JDK 17 changelog #1641 --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 56d9f6e178..5f09616908 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,9 @@ jsoup changelog * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods. <https://github.com/jhy/jsoup/issues/1628> + * Build Improvement: added JDK 17 to the CI builds. + <https://github.com/jhy/jsoup/pull/1641> + *** Release 1.14.2 [2021-Aug-15] * Improvement: support Pattern.quote \Q and \E escapes in the selector regex matchers. <https://github.com/jhy/jsoup/pull/1536> From e4ae6faa38e3452a639961dbb834adcb2d104d2c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 27 Sep 2021 13:43:05 +1000 Subject: [PATCH 656/774] Per spec, only foster incoming nodes if current node is a table foster target --- src/main/java/org/jsoup/parser/HtmlTreeBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 8a06312b6d..aaf16dbf55 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -10,7 +10,6 @@ import org.jsoup.nodes.FormElement; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; -import org.jsoup.select.Elements; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -20,6 +19,7 @@ import java.util.List; import static org.jsoup.internal.StringUtil.inSorted; +import static org.jsoup.parser.HtmlTreeBuilderState.Constants.InTableFoster; /** * HTML Tree Builder; creates a DOM from Tokens. @@ -315,7 +315,7 @@ private void insertNode(Node node) { // if the stack hasn't been set up yet, elements (doctype, comments) go into the doc if (stack.isEmpty()) doc.appendChild(node); - else if (isFosterInserts()) + else if (isFosterInserts() && StringUtil.inSorted(currentElement().normalName(), InTableFoster)) insertInFosterParent(node); else currentElement().appendChild(node); From a8df71b3f520e528406655ab4a73c369c83d8aac Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 27 Sep 2021 14:45:30 +1000 Subject: [PATCH 657/774] Limit the stack depth we scan looking for mis-closed DD / DT tags Crafted HTML was getting bogged down here --- src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 0bf7538209..4074cca349 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -593,7 +593,9 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { case "dt": tb.framesetOk(false); stack = tb.getStack(); - for (int i = stack.size() - 1; i > 0; i--) { + final int bottom = stack.size() - 1; + final int upper = bottom >= MaxStackScan ? bottom - MaxStackScan : 0; + for (int i = bottom; i >= upper; i--) { el = stack.get(i); if (inSorted(el.normalName(), Constants.DdDt)) { tb.processEndTag(el.normalName()); @@ -656,12 +658,14 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.error(this); return false; } else { - tb.reconstructFormattingElements(); + if (Tag.isKnownTag(name)) // don't reconstruct for custom elements + tb.reconstructFormattingElements(); tb.insert(startTag); } } return true; } + private static final int MaxStackScan = 24; // used for DD / DT scan, prevents runaway private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { final Token.EndTag endTag = t.asEndTag(); From d3f4e319e009f7af8f1305279495d05e8488b089 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 27 Sep 2021 15:18:04 +1000 Subject: [PATCH 658/774] Flyweight Tag.valueOf in TreeBuilder Lowers memory consumption when many repeated custom tags were in the parse. (Known tags were already re-used) --- CHANGES | 4 ++++ .../java/org/jsoup/parser/HtmlTreeBuilder.java | 10 +++++----- .../org/jsoup/parser/HtmlTreeBuilderState.java | 2 +- src/main/java/org/jsoup/parser/TreeBuilder.java | 14 ++++++++++++++ .../java/org/jsoup/parser/XmlTreeBuilder.java | 2 +- .../org/jsoup/parser/XmlTreeBuilderTest.java | 16 ++++++++++++++++ 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 5f09616908..13f0353171 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,10 @@ jsoup changelog memoizing the </title> scan and reducing GC. <https://github.com/jhy/jsoup/issues/1644> + * Improvement: when parsing custom tags (in HTML or XML), added a flyweight cache on Tag.valueOf(name) to reduce + memory overhead when many tags are repeated. + + * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index aaf16dbf55..1c82827713 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -131,7 +131,7 @@ List<Node> parseFragment(String inputFragment, @Nullable Element context, String default: tokeniser.transition(TokeniserState.Data); } - root = new Element(Tag.valueOf(contextTag, settings), baseUri); + root = new Element(tagFor(contextTag, settings), baseUri); doc.appendChild(root); stack.add(root); resetInsertionMode(); @@ -245,13 +245,13 @@ Element insert(final Token.StartTag startTag) { return el; } - Element el = new Element(Tag.valueOf(startTag.name(), settings), null, settings.normalizeAttributes(startTag.attributes)); + Element el = new Element(tagFor(startTag.name(), settings), null, settings.normalizeAttributes(startTag.attributes)); insert(el); return el; } Element insertStartTag(String startTagName) { - Element el = new Element(Tag.valueOf(startTagName, settings), null); + Element el = new Element(tagFor(startTagName, settings), null); insert(el); return el; } @@ -262,7 +262,7 @@ void insert(Element el) { } Element insertEmpty(Token.StartTag startTag) { - Tag tag = Tag.valueOf(startTag.name(), settings); + Tag tag = tagFor(startTag.name(), settings); Element el = new Element(tag, null, settings.normalizeAttributes(startTag.attributes)); insertNode(el); if (startTag.isSelfClosing()) { @@ -277,7 +277,7 @@ Element insertEmpty(Token.StartTag startTag) { } FormElement insertForm(Token.StartTag startTag, boolean onStack, boolean checkTemplateStack) { - Tag tag = Tag.valueOf(startTag.name(), settings); + Tag tag = tagFor(startTag.name(), settings); FormElement el = new FormElement(tag, null, settings.normalizeAttributes(startTag.attributes)); if (checkTemplateStack) { if(!onStack("template")) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 4074cca349..e437cd9813 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -891,7 +891,7 @@ else if (!tb.onStack(formatEl)) { } else if (node == formatEl) break; - Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); + Element replacement = new Element(tb.tagFor(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); // case will follow the original node (so honours ParseSettings) tb.replaceActiveFormattingElement(node, replacement); tb.replaceOnStack(node, replacement); diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index e21d70c586..f895c9ed28 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -10,7 +10,9 @@ import javax.annotation.ParametersAreNonnullByDefault; import java.io.Reader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Jonathan Hedley @@ -24,6 +26,7 @@ abstract class TreeBuilder { protected String baseUri; // current base uri, for creating new elements protected Token currentToken; // currentToken is used only for error tracking. protected ParseSettings settings; + protected Map<String, Tag> seenTags; // tags we've used in this parse; saves tag GC for custom tags. private Token.StartTag start = new Token.StartTag(); // start tag to process private Token.EndTag end = new Token.EndTag(); @@ -44,6 +47,7 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { currentToken = null; tokeniser = new Tokeniser(reader, parser.getErrors()); stack = new ArrayList<>(32); + seenTags = new HashMap<>(); this.baseUri = baseUri; } @@ -57,6 +61,7 @@ Document parse(Reader input, String baseUri, Parser parser) { reader = null; tokeniser = null; stack = null; + seenTags = null; return doc; } @@ -159,4 +164,13 @@ protected void error(String msg, Object... args) { protected boolean isContentForTagData(String normalName) { return false; } + + protected Tag tagFor(String tagName, ParseSettings settings) { + Tag tag = seenTags.get(tagName); // note that we don't normalize the cache key. But tag via valueOf may be normalized. + if (tag == null) { + tag = Tag.valueOf(tagName, settings); + seenTags.put(tagName, tag); + } + return tag; + } } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 5fad99e2c7..97ef372c37 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -83,7 +83,7 @@ private void insertNode(Node node) { } Element insert(Token.StartTag startTag) { - Tag tag = Tag.valueOf(startTag.name(), settings); + Tag tag = tagFor(startTag.name(), settings); // todo: wonder if for xml parsing, should treat all tags as unknown? because it's not html. if (startTag.hasAttributes()) startTag.attributes.deduplicate(settings); diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index 8cd6bdd53c..c254e8e9cf 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -298,4 +298,20 @@ public void handlesLTinScript() { String out = doc.html(); assertEquals("<body style=\"color: red\" name=\"\"><div></div></body>", out); } + + @Test void customTagsAreFlyweights() { + String xml = "<foo>Foo</foo><foo>Foo</foo><FOO>FOO</FOO><FOO>FOO</FOO>"; + Document doc = Jsoup.parse(xml, Parser.xmlParser()); + Elements els = doc.children(); + + Tag t1 = els.get(0).tag(); + Tag t2 = els.get(1).tag(); + Tag t3 = els.get(2).tag(); + Tag t4 = els.get(3).tag(); + assertEquals("foo", t1.getName()); + assertEquals("FOO", t3.getName()); + assertSame(t1, t2); + assertSame(t3, t4); + + } } From 4b46397a3c6f18f88cac76c6e983e2aa6cbef230 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 27 Sep 2021 15:43:21 +1000 Subject: [PATCH 659/774] Short-circuit tag scans for custom tags --- .../org/jsoup/parser/HtmlTreeBuilderState.java | 7 ++++--- src/main/java/org/jsoup/parser/Tag.java | 5 +++-- .../jsoup/parser/HtmlTreeBuilderStateTest.java | 17 +++++++++++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index e437cd9813..ae461e92ef 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -632,7 +632,9 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { break; default: // todo - bring scan groups in if desired - if (inSorted(name, Constants.InBodyStartEmptyFormatters)) { + if (!Tag.isKnownTag(name)) { // no special rules for custom tags + tb.insert(startTag); + } else if (inSorted(name, Constants.InBodyStartEmptyFormatters)) { tb.reconstructFormattingElements(); tb.insertEmpty(startTag); tb.framesetOk(false); @@ -658,8 +660,7 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.error(this); return false; } else { - if (Tag.isKnownTag(name)) // don't reconstruct for custom elements - tb.reconstructFormattingElements(); + tb.reconstructFormattingElements(); tb.insert(startTag); } } diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java index 1f43ead474..d573033a56 100644 --- a/src/main/java/org/jsoup/parser/Tag.java +++ b/src/main/java/org/jsoup/parser/Tag.java @@ -237,7 +237,8 @@ protected Tag clone() { "ul", "ol", "pre", "div", "blockquote", "hr", "address", "figure", "figcaption", "form", "fieldset", "ins", "del", "dl", "dt", "dd", "li", "table", "caption", "thead", "tfoot", "tbody", "colgroup", "col", "tr", "th", "td", "video", "audio", "canvas", "details", "menu", "plaintext", "template", "article", "main", - "svg", "math", "center", "template" + "svg", "math", "center", "template", + "dir", "applet", "marquee", "listing" // deprecated but still known / special handling }; private static final String[] inlineTags = { "object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd", @@ -245,7 +246,7 @@ protected Tag clone() { "sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup", "option", "legend", "datalist", "keygen", "output", "progress", "meter", "area", "param", "source", "track", "summary", "command", "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track", - "data", "bdi", "s" + "data", "bdi", "s", "strike", "nobr" }; private static final String[] emptyTags = { "meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command", diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 29482c8428..5257975a11 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -1,6 +1,7 @@ package org.jsoup.parser; import org.jsoup.Jsoup; +import org.jsoup.internal.StringUtil; import org.jsoup.parser.HtmlTreeBuilderState.Constants; import org.junit.jupiter.api.Test; @@ -10,8 +11,8 @@ import java.util.Arrays; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.jsoup.parser.HtmlTreeBuilderState.Constants.InBodyStartInputAttribs; +import static org.junit.jupiter.api.Assertions.*; public class HtmlTreeBuilderStateTest { static List<Object[]> findConstantArrays(Class aClass) { @@ -47,6 +48,18 @@ public void ensureArraysAreSorted() { assertEquals(40, constants.size()); } + @Test public void ensureTagSearchesAreKnownTags() { + List<Object[]> constants = findConstantArrays(Constants.class); + for (Object[] constant : constants) { + String[] tagNames = (String[]) constant; + for (String tagName : tagNames) { + if (StringUtil.inSorted(tagName, InBodyStartInputAttribs)) + continue; // odd one out in the constant + assertTrue(Tag.isKnownTag(tagName), String.format("Unknown tag name: %s", tagName)); + } + } + } + @Test public void nestedAnchorElements01() { From b14eb2a26c3fcbaa9d95edb569692baa0283c4ca Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Sep 2021 09:21:45 +1000 Subject: [PATCH 660/774] Test case and change note for parser improvements incl tag flyweight Fixes #1646 --- CHANGES | 5 +++-- src/test/resources/fuzztests/1646.html.gz | Bin 0 -> 13460 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/fuzztests/1646.html.gz diff --git a/CHANGES b/CHANGES index 13f0353171..be9fc196ce 100644 --- a/CHANGES +++ b/CHANGES @@ -28,8 +28,9 @@ jsoup changelog <https://github.com/jhy/jsoup/issues/1644> * Improvement: when parsing custom tags (in HTML or XML), added a flyweight cache on Tag.valueOf(name) to reduce - memory overhead when many tags are repeated. - + memory overhead when many tags are repeated. Also tuned other areas of the parser when many very deeply stacked + custom elements were present. + <https://github.com/jhy/jsoup/issues/1646> * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing optional closing tags. diff --git a/src/test/resources/fuzztests/1646.html.gz b/src/test/resources/fuzztests/1646.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..ce142618a72fd041d9a5ef34e24210864c2abfff GIT binary patch literal 13460 zcmeHuc|4T+`@WDUq0}*wP$ZG1tXYOS=TwMN$~J~<iI5n{L^Fj-rR*_Mr-M_ngtD8N zRJJUo?2KYevW!NSVa9x)Sui8@>3m-2_xmod&p)1-d1juO=YGHM>%Ok*zDY&!^XqdR zcH~;MOGiyhRr#dfX-}@<Wb+8fWO9GRtL%GXdE)${5zRG!3>Dt*$l7v!x!Oqw<h|72 zu1IY5e^i60TK?#5{x6rDT;)Kc?HSlxkhrk!*O!%)vcpO*+w<lA1tBbV_Of=dweU&~ zn$;(e4G8e<H#c>K5XdMZajazfL$9t=-9cYd67yz+gZ&H1<AvlF@7c-Iv$!2n+1-Tq zo+ZQCxnq*hD%$u^;3lX&HJd7|b~rfX%<M>DE*DZENT2V9hugBtC)x(pysVmA3U2Bh zysx(5f!c<ED*H&IUK?vJY)siE(IcnY6+zl3Pl8(2#C$h+%D!oA-4FU?S|k`}A^7?L z<Is6qi`wsa&pAO(&{=()REuuw$S5#ZN(CiE`-(4l=vUC!r@XK3l`3ypw{A2;Ww&HS zHup+1YnvMEaZ-S7Ht~q&$X9Fo0g{(F?e~CGJ3bQyzWzUfgZn-naa{>q*)lbs8jBJ{ zEqL!lg5jjo1c=W#Y-=3Wq9#2+-<al9d<+(B=2en4D0AZ3vOSDXvM%uBLvx*Y9PN<} zDoBB<{IxdkL3Fgp(l@Z=UCqA;_fC~<bG#Jx?1f}A-fK8OM^9hRqrRabssGa(YPh8G zc(7qd>PlA!yOcg16?yzlD`Pd)9~^x8Jl&dnba=Z)?DpSAp<MMdVo9>G%-FWdK`Cn< zj%^4(>*4QZO1i-XDvXJ#bx?STLH+okYUG|AjU8ft7v9!RR;OA>;*1FSxe?bgUJ*m! zPp!<!>OS+-<~aINQe1wE8p<)+Yx_}X(6?ZsWRyAuYLO4DuVroo)_z#>uu-R@P5m+# zQb6FNITts-eO)CCxn|d2J4efEBd^Pab0K}W<zA`pbw4o6eH*y7c9TlUdT{x4E$p>u z<;d7`%y}S6x*MumRY>484;KQV@7ZASrp>Mr(#~czE;%tL9Dd@^g>ockKYCdd|CYE1 z-Iq*I^HETU$68!N==|iN#<u%Fl%(d#lO~;xkdfwKv2K!3@3d?~;7?p&i<@pzSgp(_ z7-7$~zCQIq>&zJWhpg}`lPM3o95>cyzAsX%*5Y>Pv9M6Sl)8@R?2iu>ys7?Xe0l8e zit#>kyH8fv^Mz60suipy6j2JlCl?KlwXvl~iWI;8KyC%!>eE+sAJ=xZ6Q57^I{lrE zD8kLZRrU&a8Nc*74XF-+-(TOW!AvTHcgAhx=6@~_18NdXl_RhE^(U5t{Vk+A?kJRV z_zmqV@DI`#fej*kWh*G8YR$fc5AcZozcdtW6DdW_?Hut4=sSoG1Z@*{72pv+SVkdv zPfUCs7M+8NB{DrM(AZo!#Gf=3PSjYwoboxsta3EtBJxOFobPBXrK@>xCgz6jg}CFz zr5#P^HxRZ>(f-EpRsE{~Z$m*;?mMp@Qo}9%t&P+K{V+t-{j2n#4nXiv^1irLH%acD z83cCb2AP0lu|3VvKe@a7&xYaAkA5N88>3fINkgCdWPhE<yd`erd*^UA3I|}V_wMgF z6YgJRI|C6i7j8%#8yid0Iqyev3^ys0YJ@#RivO*gwG%Qb&=s?gl~1>BIF+h*08htZ z1wL3c!F*V#*T*Zf{>FjwHC#OLQQrakd01rBgRR>#&+be}X0HtJhzMEvHpeYOo1xnL z%*DUxpu@Z44&~uH#)`+FAMfW#yK*V1zm;|(hN?0~6ahmnYzDd2O!WkxC0}^}`Sz1O z5knEPdoL1Px9zBgO8au`F7~WiG81FGf4TKds?i~nftw3|@cdrw0+d~hP0jezdK><r zk2k$m%XkkL_ByZeb@#BhQPnCCTSk7ekjxcSbxry7Z%|WOH;hpLaU^NR+Gcu%?|6yH zPO0~`h+XZ8UT^tJ9KB2ztMicG?3_Zo(&ACyT?igQyO#&d&f0tCkzJ;0-WDq!X4V}t zcLK9u6-$O8Qt4sHe}K52e1q715v1)h@K&&|_Rhb!^jxvm_I;M;-Z~xz4*4?RO<lJT zhJ{C4k>(VB*3(-is=_xF(#5TxT9m(wXYL9sEjO##j8i7MS`?wYOzFk(WPb9`wUl<V zo|Hw=#v%nav11Zk8k;i2J1cH@=#t@8wYoz#QDSaCPY0Oq1kYM9i(Z#d1;#=l-f`ru zP^Hz&<3#C;_f{g-IwjuEmkmi|rNvKQ99k9)z;t8dv4RTKoTGGLhN<5IAf!t|geP4I zl86XT4|l60lLMPJ>KD^;DpI?JPnM`xeljL2<G6qj7;$4)nQy!#ysP5+wSM(MyG%d# z6o`@zolGc#rYi0CjV4`09Em%ongg9ah`}hBkSogw<bl#uPYS9N25H&*VhCxxr`gW8 z`P^t=nNQt%`Nr`ejKQF}3{(pM-z|qunC^;+i!r!Oostjm#!=ln+EyFP3M$&E<Bfr@ zpdi{+s9PUbU{dOiE?GYx?QBHb+nbw}=(~>J9nw<~d@R4XqQ%7P9o?iWfi`3?H4)#I zz|%#k{ZV<ZE;V?JuAyvwr!@-VQ9HC#{Ru!e4RD!yFwI~D<F`xqWsU8rqEiA+;<OJm zHS!mWECdR1@{q?#yOd~_Pqim4rXio=-S)}L%Y5^B&Z{l?jNBF&<s80xL;KnQtN4<q zxKJta%V#b`5lY#a)OXm|?Y!iJd-*B6mD7^YV7`TfSoEk&APX8qYR1;MF~H;yvL`JW z1Lk2u<7M8+VcspShg^Ds;-$n*gqHDsU?E89$Swwn^pKbuZ*qf{o-f1k0_7_I8KUE@ zH#eV|^^)tvda)*`l*w!|Ivhb(+v(egO#U2OS^rAq79CDZcHPp_eM!@pLHGTbS2`#_ zPzdOReqU8veVPoAyZ5#fPK|^{;IaZ)cf797J?(I2Ow0KLzAx5L(n7pG$1V_7{8M}O zFI63WYA@AZru^WKR6qAw6~^{vEsK~(&$x$qw_d^r(?*|bZ(p7&#BlsFX0RFM<PZM1 z^Y$W!Ipn?p>}<zOHZdfh<`6R0QKu4+NT8LSSrcq9S9j>l<D!EvQbMbf4jaG%sJJv# z!b8qv3H;f&(hfmC|0ynIYR&I!Hnz^AI%fYoirMGh;XtwD{hhkHUXvxNJ8wG?fDQh# z+>;8xwT<JAY;6R<eIfg19)m;SUD6k&Z!#e&_qnJw8s&I8B5gtUEXe^L))>;x2kPq# zPb9n#${j0J&AH2_%9y)9*m)0~n5<&ZWp03!w8Yt-Qr7X%n=0j#!g}wyz!Rw^!Xvj= z6j%cMSXYgcTd!Ev=@x%|-aR9`RW5&<w0@Tn#GAXB=^R5?q%w+iDo9Yvh_foGxX|E0 zn4j<3&5M(M$x_PsV*YzfYKoNZV<sy8eIA3u6H^RkC~Zw{=7epFQ*mxvIGjQnI<u`h zpYvG)4(gF;TUVA=G>Od&@tW}VR_+WO-c0PG_Phdnl0H2yD(qL0>O4Q_aJCnpS%;U@ z3M>gk7gga(+tksmo#bwOS-H+ofN<e^6!m0u2<?*8g%+PP33arP(QtX;kn)f-82!P@ zfb=5{kU5sK6`2I%rU|m10-Fog=cJ3hB7RhSs(?m`;vguip0X1Z^$Npde(s9XCm!8X z4%WGg@2jl=R&aQ|thG=0dvidSzqJGbIACq|C>JqY@JG$_+!m)3#__JwMAJ(&kBN|K zHg?Mi47)KHOH`ja;iYC0G%Q;wvt;dZ1{aD^xwsf2=z^?mYGm~lpwcMU8J452{`k_x zB0RK#bei8}CGDW#uo?YVhJv&=@2iJtt}f{wC8!usGgG+fDagpfg+G{IN3TjPW1a97 z=*Ozz)wg6EtBIdx#bmHwur|c<v^yaT=lE>><%LhOJwhHuLObSjcK(EwDt0dB-S~a4 zqJ(9qq8WI5N@SZ_nz*qE>Er940C)Dc;47iV48l5xrmrFGA3zyXpCx%kj><4gYoB=@ z`$rb<(<RQ~GXj{U=Q9k%7iUb5-c7!pFwOEHo)bMW07Q?DejR<R0|t91KG-)Y1uJ|v zv+Q(yKM10L!%NU_cfa(x4_BQ=9bFR+->0NB+%=eikI}lFl2-1qFV1gJj$;tV^@0?q z>@8Ca&Y%ct1**IQaB)g+_ZY>m+d^~BSAiMH$#^h<#J;fqn&_u#eTS6s^jp~yydp2w zFau|tQWZ>zE-4_+<_{0?Nri;1S5EYmQ%YSwYd9PqEZXeY)8M*&$s4y6{{&UZmQU~6 zly}}7c2|t=TP0OvvT1u{@(rkT0i<PIguiI8^jW9H*=p-vEJ0(6KUnm$#JTGTKpiGp z0dggUc#M;2$T;gSI6E1tqiD)x#lCd39(k&N(7RmOK+SXGk=@T<1?)?1L1`+>((B>z zFYv-!93|$TfxDQL2l5=MZR(5B-%@CoG3GvxabNIEy6~+45ix8`>Y08j?=ATl&aA<5 z?2^QA|LFHL(V)S<R6(539W!}uF|8%#H7hdKi5^1$f4pJWehd;UG%D8SK@SC0&Ed7- zKA$gLtHjWdF&0{GOKdWIV55*d9@TdR*siqBcZv&MPK`$yb8|4i)S=y@%pgAq^80Tl z18*r%UpB``xfwIqa<?>4wc^gb59?AR-uoapkda~AFbUePYm~-I0d_D}-E@gsFbT!c zPSUWm{RK(T5%mUIQ1506oP)_>`*^jy3R!?-UvoE-Jp0j3bbs~wLT<WjY1{$+vgP__ z?Hti5*2*DnJ8ar`#B5jYsnk+uCYe*q@v6_E9Dkqnx+nnFITGOLyt%S|tlc-_Lff?d z*0lWR8=z;n0VY^-bHX}gCArv$%V;B0lQ@UU=LYLkwEuFDVbXGvVUB09h}m({dEfS7 zwTww;LZG7qTWYXm9i{3rCpF04QSgqb9abA4ahIq0HaSVpd9Z{MCWY(B8a&T`xzpdC z-`ae!V==(bgOl{P2U*e2G%PaU{uwvKJp*9^2E(i#(sOp0ZdY!+yXme5fj~HT_UotF z3ZeM(;CsUs%Xg_}nUhc~?a5|Ym)GLJ7beJg=?!=54|A5tYY4V&v4rGb*aj5>Y~ug_ zai$4PlRt;ZgH)VSToODfbafQ(+GE^3q<-N}9E#x)<jY@^OJ}gko2X&W4SRazwVR1k zBsOniQQ`e`(1+R{T_AMKfFVDSYk5m0Kukey!QH*u6WN6C!T>4pJIlPapJcILwm5S7 zKZ~E0N-VY>{2EITY7<ZBLY2O9fD~2?)KcABW0@bLTt{R5{_pGTfscZeT%rtvQ^+jb z&R_eb>p65sUPYP`tM>f2iw>kQ?+15rWNA!@a2?Z1XWHfeFtGd8`=jJ|2c-+oR2*bl zC!-b<@Yj=KghWH*EX;a3??QT!uSz+t!y*_IC{6Yp+C8zO&f6fEIIBYQCBnuc^3sZ0 zI!`6)W@{u(O%%)&B;{q(hV93`RO8BFcsJ{eM1TPpHb%@t8GPS4meb4h-ywI-5{C-A zh|jBjy_rmSivMLMcRa|{)IC(A{3CC1d2Vms>^hSFB`%~kb5m8XG<T-@hCSVp_+4L% zg;LvjsDt?NzWffSW5nuumJguj6ExDG7V4{MWqg?FcFnO?t7=SEctY6y#;bYbd?{kK z+26{ixfT}-FIi0t1`kEP;q}ll2SRplA4&Nkknvc>wlOsv6y?FKP{e(lL1ByKh*7)a z=C%8E-m#tp-^AONiued3t7X8%I+#m6wfANRwD^?9k+#@b$&a0PhH9(pSMdy1|G1ZP zTEl06a2)<W);O;*FUer|HuO|}Dp5$;<s2#C2X>7F`h_P?02B;<{&?V!C&ilNSJ^l_ zU7QvEdliHbdTZSI-|6Y->F(AMxc!r!JA2Qjrr;Qpu(KLF@fA%KdHobhsXM*r+WNtQ zxFR3&$bs+G_<_4)>@tj#{>(pPpilhX#^L(Vb=B4G&3QHo<`%3kgpkNbN-DHbwi#!g zqP%%Am{g{t{(VSesQ)kl<3W_5D}nvZxR0Bw0qS_=Qk9qxOB_jC2^pDe4cY$m89m~7 z6-7?Bidb1$Xi$CDp1t?7bnmQ(W#E|Z7YX4OF&sT*@J#f2f>A7j2nlvp(%}`kEXtBc zVpllDN(8F!y46GRW81gT*%!;MS;Cro`%*%?fgA&MGa2}Zw>iW{$~b=!DY;}(Z)gG5 z*&f~7h&`x_2x0TpbfW@x-0ATPY+1`Mop77!uIwbIdU%*DP@=bjJl1bgGe1QP;5xDx zA+y~i3p7F1^WP8Wq%JL?!GtxmmLXcyk?%F>{4OFgw5#3gBTj3g1)<h90dgyus|)2? zOph12zHlAUA77eti~UD5)0b>X2{~8W|3T6G2IZQZ5vx3$7r|g5?#p>-t%HEnRQ0KO zyOM9h1T6q`OB85@lX~Fff|wgA(R2oUB>{5iKvkki{RUx!Y_Y5&pvDCYx4Kak`Ud%y z@t$Fsw{PWhrD%O4!zJL;x+U1k6vt4&{u3~94+S5ZNyT0E2TkSOnM);3U)qvyx}|oo z+P$Fiad&hwAr$c~bdsXN|JSj11+cC;=d8h&OWz^tizy|D(Szbgr1V&!duto!jUG0v zV7zuu3rly9cUzH}lG0eDGk-yg-RgOg?C?VHkszE%+Tv6#b5R<_Y9X~;U?j8qX&!X& z^iag{>`6werCHO*mf33ITp-S8fstCw_Pf8XAdd;7vj>KjkpLTQIlb@Z=gvoRP5-%G zTg0x>+Vb#uPU6QahAZ>=(xJh+W8@z?b?T2|Nj546ZfYZOUmFgVu@R`!<_z{PO)G02 z+*{J(a`YRLJ#b=mY4C7$(u!RufemJdi>+3RC9qwG6>LTy&S)LhqG1&`zh;V_erre& zZJZi8AvNS3Ttdd1jZx5A%Fi3-XA&5y$<)lJH!O+Ck9tyv$O%N1dJRO39YT?zpt?w+ z;OE<rJPW7}TCo01G+EvR)A}2(K?S*04hM_<zSVa~50Iy=@tHWl@l<^LWxae(0Qt$_ z$;I~6v2&XT<99ti{pMd`$j`V@k*0rf_NUr?P3q*_ZmdXY=>hDC!q>FJEf^<3yGw{X zh64MsBgG=LTLj?&?^uFdZv`ho_=jedGWnFLlvYP()0aLE;(PA=xn@*KidNm52Q$LD zcAu#ipP8(GjroQ)Y?YWA89>ARrbf%VuH(h0X3`rBLMFbpHR%{fueM|FWuKVi5{9Z8 zM$$W(`jN51Jh??<#B7ba%zpc(&Y7NOu+~h0me@!s?DphHuurd)W}siQo!wZxuveLX zPSF=qb3f^3QfXHy&aTh9+cOdBIRa#-J#OPf$%@WX^}+eoXc{)M+@nzYbw3R}RS_Ky z#-X))?vD4CG%4gLvt+rd=r=@x#L9I_M@VqBl`p?EzdXyO4YgtCiHVNVipBZsFQ{1_ z5<_on@Fls>hd(A(Mky6me5SAKz;;2(^njAQWgBL~_OOd1FkPocZ=MCDVSn5_lXU3W zZswafb|R@xrQ&#8Gvq3g@x?V_m^nJ4DyS4o&pgT1&O@H?Nxm0AR~InUp!F|)eChe} zNcYkzva~4V2=kD^^u=&U$3)dN@Q?dgCy;i?Kp#_`x+(xeQp;Hsk7;?@wikT}NI1Qm zO<%KJO**!xg|n-{5JSJ1a{7!s0<+HYqYYfh4RZ}lZI<WtqhUCXbL21aoR0X}c$oI9 z?d9hIn6cz%%_21^{b06^<8mydCVx);@ic_qQuBN|zaOs&AoJ1Vu$JHc+(!^RLEQDI zdb%~?RI16$&<RGv#RiK3PwYlA8?#6RmD!lB0gvc6m=kum^v;d7i5*>=%j|>?hI(Xa zBeHNji~YSnzg``x1Q!L(jF@-SdyRf^v-<Fd!tv)8HXnrF1rKOBO7hKmrp?c2{$0bb zwC_HnJot9_Lxd5xO6omocWA_;G&aHQFGcof-7iN}g09RBKassSdyGpBKJ9*v0nBu! zkzaFm(X<}NVBWtk;u`9i>^iQnqPR1Ys!XYpzCIYONBD`^jDOn?(;8;=iA4;hWzTua zgVZi<=fRawsyI-@KZPAd7MqDnocm8do()j`(~tkZ-;e(;OJ|-ye&GH8muV}g@Nu;0 z_4p?ZJ54o%>D>^MT|RQ&u*7mxlY0Bes|t{I(U&as&-9r`MK=xxz{!k(5f;nf?8ANC zS_qJ6+ZBq;i8Uhuz&#gaS~d#Hh<ut@VAkCM7*pKB8Ys)2;Xv#~KIBs8x>uWWjHzKQ zc7N)^fM#0hJzKiBqFjfG1>ANS{B|4T`2HWb7c7gx?+jmX+vKgqosOfjA_VoOzm5Sd zHd&pI{J~9ajz{8Hh7HLkn;|s!(|gmY;t3Nr*}}v2@rU176svR2B}QnB(mRryUi)l4 zsr}o)2@N|3cK>IdFal9L%xj{r!Y|zlCw+CG4O3et!R;YAX!3A>ylx%PzAoekOR+IZ zAci{F8xmg3sS0F#F2SiBt=+<F;k!JGp*j3&f$75s{6dVeKvg;H%)zEkui&vSL6afm z_#mK}htjAk>p#}i8JOAjq5$26?m}IldQT=sn$J!=RRq`Cyzivj<n{|*uFG8(fX{om zWqG|Y^r|hQI<ULIyv<N;GZA2c>zk*piMEzdTK)4IrjJy$tmfwb$l@r9dbh`G#hag< zWRJn38p&A0M}l9o<6EMeydtg3<aHb7{NdpuaOJw1HV1{@uneAtCn&K^)}=Xa^ZV#o zh9$gg&iG~sZ{AO0sVVlpiGw=Urd|}-rJrt>nrsJxJZk)HyP&${&zo`pjbm=+Em%G6 zG?c6Jdt>s)pvAU7t&koTvlVJSxnmpPyiB`<`Hvk-4mfeX2=w!!Eve{4!tqn#5Q<>4 zcL=c{8KUXpelrx;e=!_RJqovQ6YdvpzqZ=BH}4CCuaNG~0fKtd5^cY?8y{5A0Q$_# zgPi=L_Y^{Zy+_@Of<$UZ(yAnM-Ra;qyC+5YWa7s;CE=~?)n$jS?x>ro`)#(rcXEVA zQknG9LTk_TpP><wI=suyUGbiUowI!TzOgkWTJ);b<Avz@o)9k%UO{>BG$fg4qw@oQ z0~O77%1*e8dspl>X0vPzZuImM`ALN_URzvw*58o^8-uFMCVwD7Wx~oIG-}@xx*E&a fXzjhL9@A!KvuE+t{-MKTv}LgdJrj}T!d(9akj2u( literal 0 HcmV?d00001 From 89de79670c77039a99177fe6f739ea6f675a3a59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:24:02 +1000 Subject: [PATCH 661/774] Bump junit-jupiter from 5.8.0 to 5.8.1 (#1645) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.8.0 to 5.8.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.8.0...r5.8.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 99b0c381a8..7d96e16ab5 100644 --- a/pom.xml +++ b/pom.xml @@ -301,7 +301,7 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> - <version>5.8.0</version> + <version>5.8.1</version> <scope>test</scope> </dependency> From 0d1f04a04ffc26592a8dda787a1fd0d707285e0f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 30 Sep 2021 15:39:34 +1000 Subject: [PATCH 662/774] Javadoc update to add @since 1.14.3 --- src/main/java/org/jsoup/nodes/Element.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index fb295208ef..7c7c36cda3 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -509,19 +509,20 @@ public boolean is(Evaluator evaluator) { /** <b>Beta:</b> find Elements that match the supplied XPath expression. - <p>(This functionality is currently in beta and - is subject to change. Feedback on the API is requested and welcomed!)</p> + <p>(This functionality is currently in beta and is subject to change. Feedback on the API is requested and + welcomed!)</p> <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an alternate XPathFactory implementation:</p> <ol> - <li>Add the implementation to your classpath. E.g. to use <a href="https://www.saxonica.com/products/products.xml">Saxon-HE</a>, add <a href="https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE">net.sf.saxon:Saxon-HE</a> to your build.</li> + <li>Add the implementation to your classpath. E.g. to use <a href="https://www.saxonica.com/products/products.xml">Saxon-HE</a>, add <a href="https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE">net.sf.saxon:Saxon-HE</a> to your build.</li> <li>Set the system property <code>javax.xml.xpath.XPathFactory:jsoup</code> to the implementing classname. E.g.:<br> - <code>System.setProperty(W3CDom.XPathFactoryProperty, "net.sf.saxon.xpath.XPathFactoryImpl");</code> + <code>System.setProperty(W3CDom.XPathFactoryProperty, "net.sf.saxon.xpath.XPathFactoryImpl");</code> </li> </ol> @param xpath XPath expression @return matching elements, or an empty list if none match. + @since 1.14.3. */ public Elements selectXpath(String xpath) { return new Elements(NodeUtils.selectXpath(xpath, this, Element.class)); From 80a939612f702962ffa7f0fd121fedc2040e0afe Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 30 Sep 2021 16:42:03 +1000 Subject: [PATCH 663/774] Javadoc update for XPath --- src/main/java/org/jsoup/nodes/Element.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 7c7c36cda3..fa541d5661 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -522,7 +522,8 @@ public boolean is(Evaluator evaluator) { @param xpath XPath expression @return matching elements, or an empty list if none match. - @since 1.14.3. + @see #selectXpath(String, Class) + @since 1.14.3 */ public Elements selectXpath(String xpath) { return new Elements(NodeUtils.selectXpath(xpath, this, Element.class)); @@ -539,6 +540,7 @@ public Elements selectXpath(String xpath) { @param nodeType the jsoup node type to return @see #selectXpath(String) @return a list of matching nodes + @since 1.14.3 */ public <T extends Node> List<T> selectXpath(String xpath, Class<T> nodeType) { return NodeUtils.selectXpath(xpath, this, nodeType); From 00061629b5b91f09afbfea7eae3710bb3253c5b9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 30 Sep 2021 17:08:19 +1000 Subject: [PATCH 664/774] [maven-release-plugin] prepare release jsoup-1.14.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7d96e16ab5..7141aa38fc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.3-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> + <version>1.14.3</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.14.3</tag> </scm> <organization> <name>Jonathan Hedley</name> From c8605c989d4a9991840a7fea7916df6ed1c4b60b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 30 Sep 2021 17:08:31 +1000 Subject: [PATCH 665/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7141aa38fc..9fdabac317 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.3</version><!-- remember to update previous version below for japicmp --> + <version>1.14.4-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>jsoup-1.14.3</tag> + <tag>HEAD</tag> </scm> <organization> <name>Jonathan Hedley</name> From d0ca11a2c0f6d7efbd22db0dbd63567e0fec77cb Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sat, 2 Oct 2021 22:15:39 +1000 Subject: [PATCH 666/774] Tweak javadoc --- src/main/java/org/jsoup/helper/W3CDom.java | 2 +- src/main/java/org/jsoup/safety/Whitelist.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 8d38d74f1c..2ebc3ba741 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -53,7 +53,7 @@ public class W3CDom { /** To get support for XPath versions &gt; 1, set this property to the classname of an alternate XPathFactory - implementation (e.g. {@code net.sf.saxon.xpath.XPathFactoryImpl}). + implementation. (For e.g. {@code net.sf.saxon.xpath.XPathFactoryImpl}). */ public static final String XPathFactoryProperty = "javax.xml.xpath.XPathFactory:jsoup"; diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java index 986cf4d0f6..5318ae675e 100644 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ b/src/main/java/org/jsoup/safety/Whitelist.java @@ -11,7 +11,7 @@ removed in <code>v1.15.1</code>. Until that release, this class acts as a shim to maintain code compatibility (source and binary). <p> - For a clear rationale of the removal of this change, please see + For a clear rationale for this change, please see <a href="https://tools.ietf.org/html/draft-knodel-terminology-04" title="draft-knodel-terminology-04">Terminology, Power, and Inclusive Language in Internet-Drafts and RFCs</a> */ @Deprecated From 9ce6ae1ae21fb49afcc0700084fc4366490bc421 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 10:18:29 +1100 Subject: [PATCH 667/774] Bump jetty-server from 9.4.43.v20210629 to 9.4.44.v20210927 (#1649) Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.43.v20210629 to 9.4.44.v20210927. - [Release notes](https://github.com/eclipse/jetty.project/releases) - [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.43.v20210629...jetty-9.4.44.v20210927) --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fdabac317..680ed49f95 100644 --- a/pom.xml +++ b/pom.xml @@ -317,7 +317,7 @@ <!-- jetty for webserver integration tests. 9.x is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.43.v20210629</version> + <version>9.4.44.v20210927</version> <scope>test</scope> </dependency> From 6b1fbb51b501de87049b250fecc9a5076a186f6d Mon Sep 17 00:00:00 2001 From: Jairam Chandar <jairam.chandar@meltwater.com> Date: Wed, 6 Oct 2021 11:58:00 +0100 Subject: [PATCH 668/774] Allow attributes valid in html when converting (#1648) When parsing and converting an html document, the syntax was hard-coded to xml. This PR checks the document type of the output document and uses that to determine which attributes are valid. Co-authored-by: jhy <jonathan@hedley.net> Fixes #1647 --- CHANGES | 6 ++++- src/main/java/org/jsoup/helper/W3CDom.java | 14 ++++++----- .../java/org/jsoup/helper/W3CDomTest.java | 25 +++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index be9fc196ce..17cfe0f48b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog -*** Release 1.14.3 [PENDING] +*** Release 1.15.1 [PENDING] + * Improvement: when converting jsoup Documents to W3C Documents in W3CDom, preserve HTML valid attribute names if the + input document is using the HTML syntax. (Previously, would always coerce using the more restrictive XML syntax.) + +*** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 2ebc3ba741..3b0b5df692 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -38,10 +38,9 @@ import java.util.Map; import java.util.Properties; import java.util.Stack; -import java.util.regex.Pattern; import static javax.xml.transform.OutputKeys.METHOD; -import static org.jsoup.nodes.Document.OutputSettings.Syntax.xml; +import static org.jsoup.nodes.Document.OutputSettings.Syntax; /** * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document}, @@ -215,14 +214,16 @@ public void convert(org.jsoup.nodes.Document in, Document out) { * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Element) */ public void convert(org.jsoup.nodes.Element in, Document out) { + W3CBuilder builder = new W3CBuilder(out); org.jsoup.nodes.Document inDoc = in.ownerDocument(); if (inDoc != null) { - if (!StringUtil.isBlank(inDoc.location())) + if (!StringUtil.isBlank(inDoc.location())) { out.setDocumentURI(inDoc.location()); + } + builder.syntax = inDoc.outputSettings().syntax(); } - org.jsoup.nodes.Element rootEl = in instanceof org.jsoup.nodes.Document ? in.child(0) : in; // skip the #root node if a Document - NodeTraversor.traverse(new W3CBuilder(out), rootEl); + NodeTraversor.traverse(builder, rootEl); } public NodeList selectXpath(String xpath, Document doc) { @@ -282,6 +283,7 @@ protected static class W3CBuilder implements NodeVisitor { private final Document doc; private final Stack<HashMap<String, String>> namespacesStack = new Stack<>(); // stack of namespaces, prefix => urn private Node dest; + private Syntax syntax = Syntax.xml; // the syntax (to coerce attributes to). From the input doc if available. public W3CBuilder(Document doc) { this.doc = doc; @@ -343,7 +345,7 @@ public void tail(org.jsoup.nodes.Node source, int depth) { private void copyAttributes(org.jsoup.nodes.Node source, Element el) { for (Attribute attribute : source.attributes()) { - String key = Attribute.getValidKey(attribute.getKey(), xml); + String key = Attribute.getValidKey(attribute.getKey(), syntax); if (key != null) { // null if couldn't be coerced to validity el.setAttribute(key, attribute.getValue()); } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 1c172a4dd0..f1ff904454 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -190,6 +190,31 @@ public void handlesInvalidAttributeNames() { assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><head/><body name=\"\" style=\"color: red\"/></html>", xml); } + @Test + public void htmlInputDocMaintainsHtmlAttributeNames() { + String html = "<!DOCTYPE html><html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p hành=\"1\" hình=\"2\">unicode attr names</p></body></html>"; + org.jsoup.nodes.Document jsoupDoc; + jsoupDoc = Jsoup.parse(html); + + Document w3Doc = W3CDom.convert(jsoupDoc); + String out = W3CDom.asString(w3Doc, W3CDom.OutputHtml()); + String expected = "<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p hành=\"1\" hình=\"2\">unicode attr names</p></body></html>"; + assertEquals(expected, TextUtil.stripNewlines(out)); + } + + @Test + public void xmlInputDocMaintainsHtmlAttributeNames() { + String html = "<!DOCTYPE html><html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p hành=\"1\" hình=\"2\">unicode attr names coerced</p></body></html>"; + org.jsoup.nodes.Document jsoupDoc; + jsoupDoc = Jsoup.parse(html); + jsoupDoc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml); + + Document w3Doc = W3CDom.convert(jsoupDoc); + String out = W3CDom.asString(w3Doc, W3CDom.OutputHtml()); + String expected = "<!DOCTYPE html SYSTEM \"about:legacy-compat\"><html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p hnh=\"2\">unicode attr names coerced</p></body></html>"; + assertEquals(expected, TextUtil.stripNewlines(out)); + } + @Test public void handlesInvalidTagAsText() { org.jsoup.nodes.Document jsoup = Jsoup.parse("<インセンティブで高収入!>Text <p>More</p>"); From b2e749d9ff8e3580f37953801fdc63cf8d73b6a0 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 6 Oct 2021 21:59:38 +1100 Subject: [PATCH 669/774] Changelog - PR URL --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 17cfe0f48b..2d7fc15921 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ jsoup changelog *** Release 1.15.1 [PENDING] * Improvement: when converting jsoup Documents to W3C Documents in W3CDom, preserve HTML valid attribute names if the input document is using the HTML syntax. (Previously, would always coerce using the more restrictive XML syntax.) + <https://github.com/jhy/jsoup/pull/1648> *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) From 2cc2399728d32f7277597901d8b6fcce619c0f11 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 6 Oct 2021 22:44:25 +1100 Subject: [PATCH 670/774] Removed previously deprecated methods These have been flagged for removal since 1.14.1. --- CHANGES | 3 + pom.xml | 7 +- src/main/java/org/jsoup/Jsoup.java | 38 ------ src/main/java/org/jsoup/nodes/DataNode.java | 13 -- .../java/org/jsoup/parser/TokenQueue.java | 71 +---------- src/main/java/org/jsoup/safety/Cleaner.java | 10 -- src/main/java/org/jsoup/safety/Whitelist.java | 115 ------------------ .../org/jsoup/safety/CompatibilityTests.java | 106 ---------------- 8 files changed, 9 insertions(+), 354 deletions(-) delete mode 100644 src/main/java/org/jsoup/safety/Whitelist.java delete mode 100644 src/test/java/org/jsoup/safety/CompatibilityTests.java diff --git a/CHANGES b/CHANGES index 2d7fc15921..61dd021e01 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.15.1 [PENDING] + * Change: removed previously deprecated methods and classes (including org.jsoup.safety.Whitelist; use + org.jsoup.safety.Safelist instead). + * Improvement: when converting jsoup Documents to W3C Documents in W3CDom, preserve HTML valid attribute names if the input document is using the HTML syntax. (Previously, would always coerce using the more restrictive XML syntax.) <https://github.com/jhy/jsoup/pull/1648> diff --git a/pom.xml b/pom.xml index 680ed49f95..e0b0b30e32 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.4-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> + <version>1.15.1-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -199,7 +199,7 @@ <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.2</version> + <version>1.14.3</version> <type>jar</type> </dependency> </oldVersion> @@ -208,6 +208,9 @@ <onlyModified>false</onlyModified> <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> + <excludes> + <exclude>@java.lang.Deprecated</exclude> + </excludes> </parameter> </configuration> <executions> diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 1541b03afb..f6cb7c73b0 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -6,7 +6,6 @@ import org.jsoup.parser.Parser; import org.jsoup.safety.Cleaner; import org.jsoup.safety.Safelist; -import org.jsoup.safety.Whitelist; import javax.annotation.Nullable; import java.io.File; @@ -264,15 +263,6 @@ public static String clean(String bodyHtml, String baseUri, Safelist safelist) { return clean.body().html(); } - /** - Use {@link #clean(String, String, Safelist)} instead. - @deprecated as of 1.14.1. - */ - @Deprecated - public static String clean(String bodyHtml, String baseUri, Whitelist safelist) { - return clean(bodyHtml, baseUri, (Safelist) safelist); - } - /** Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of permitted tags and attributes. @@ -291,15 +281,6 @@ public static String clean(String bodyHtml, Safelist safelist) { return clean(bodyHtml, "", safelist); } - /** - Use {@link #clean(String, Safelist)} instead. - @deprecated as of 1.14.1. - */ - @Deprecated - public static String clean(String bodyHtml, Whitelist safelist) { - return clean(bodyHtml, (Safelist) safelist); - } - /** * Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of * permitted tags and attributes. @@ -322,15 +303,6 @@ public static String clean(String bodyHtml, String baseUri, Safelist safelist, D return clean.body().html(); } - /** - Use {@link #clean(String, String, Safelist, Document.OutputSettings)} instead. - @deprecated as of 1.14.1. - */ - @Deprecated - public static String clean(String bodyHtml, String baseUri, Whitelist safelist, Document.OutputSettings outputSettings) { - return clean(bodyHtml, baseUri, (Safelist) safelist, outputSettings); - } - /** Test if the input body HTML has only tags and attributes allowed by the Safelist. Useful for form validation. <p>The input HTML should still be run through the cleaner to set up enforced attributes, and to tidy the output. @@ -343,14 +315,4 @@ <p>Assumes the HTML is a body fragment (i.e. will be used in an existing HTML do public static boolean isValid(String bodyHtml, Safelist safelist) { return new Cleaner(safelist).isValidBodyHtml(bodyHtml); } - - /** - Use {@link #isValid(String, Safelist)} instead. - @deprecated as of 1.14.1. - */ - @Deprecated - public static boolean isValid(String bodyHtml, Whitelist safelist) { - return isValid(bodyHtml, (Safelist) safelist); - } - } diff --git a/src/main/java/org/jsoup/nodes/DataNode.java b/src/main/java/org/jsoup/nodes/DataNode.java index 2c37e75661..a27b99bd2b 100644 --- a/src/main/java/org/jsoup/nodes/DataNode.java +++ b/src/main/java/org/jsoup/nodes/DataNode.java @@ -53,17 +53,4 @@ public String toString() { public DataNode clone() { return (DataNode) super.clone(); } - - /** - Create a new DataNode from HTML encoded data. - @param encodedData encoded data - @param baseUri base URI - @return new DataNode - @deprecated Unused, and will be removed in 1.15.1. - */ - @Deprecated - public static DataNode createFromEncoded(String encodedData, String baseUri) { - String data = Entities.unescape(encodedData); - return new DataNode(data); - } } diff --git a/src/main/java/org/jsoup/parser/TokenQueue.java b/src/main/java/org/jsoup/parser/TokenQueue.java index 614ade1914..8d9bfee59c 100644 --- a/src/main/java/org/jsoup/parser/TokenQueue.java +++ b/src/main/java/org/jsoup/parser/TokenQueue.java @@ -35,26 +35,6 @@ private int remainingLength() { return queue.length() - pos; } - /** - * Retrieves but does not remove the first character from the queue. - * @return First character, or 0 if empty. - *@deprecated unused and will be removed in 1.15.x - */ - @Deprecated - public char peek() { - return isEmpty() ? 0 : queue.charAt(pos); - } - - /** - Add a character to the start of the queue (will be the next character retrieved). - @param c character to add - @deprecated unused and will be removed in 1.15.x - */ - @Deprecated - public void addFirst(Character c) { - addFirst(c.toString()); - } - /** Add a string to the start of the queue. @param seq string to add. @@ -74,18 +54,6 @@ public boolean matches(String seq) { return queue.regionMatches(true, pos, seq, 0, seq.length()); } - /** - * Case sensitive match test. - * @param seq string to case sensitively check for - * @return true if matched, false if not - * @deprecated unused and will be removed in 1.15.x - */ - @Deprecated - public boolean matchesCS(String seq) { - return queue.startsWith(seq, pos); - } - - /** Tests if the next characters match any of the sequences. Case insensitive. @param seq list of strings to case insensitively check for @@ -110,15 +78,6 @@ public boolean matchesAny(char... seq) { return false; } - /** - @deprecated unused and will be removed in 1.15.x - */ - @Deprecated - public boolean matchesStartTag() { - // micro opt for matching "<x" - return (remainingLength() >= 2 && queue.charAt(pos) == '<' && Character.isLetter(queue.charAt(pos+1))); - } - /** * Tests if the queue matches the sequence (as with match), and if they do, removes the matched string from the * queue. @@ -356,21 +315,7 @@ public String consumeWord() { pos++; return queue.substring(start, pos); } - - /** - * Consume an tag name off the queue (word or :, _, -) - * - * @return tag name - * @deprecated unused and will be removed in 1.15.x - */ - @Deprecated - public String consumeTagName() { - int start = pos; - while (!isEmpty() && (matchesWord() || matchesAny(':', '_', '-'))) - pos++; - - return queue.substring(start, pos); - } + /** * Consume a CSS element selector (tag name, but | instead of : for namespaces (or *| for wildcard namespace), to not conflict with :pseudo selects). @@ -398,20 +343,6 @@ public String consumeCssIdentifier() { return queue.substring(start, pos); } - /** - Consume an attribute key off the queue (letter, digit, -, _, :") - @return attribute key - @deprecated unused and will be removed in 1.15.x - */ - @Deprecated - public String consumeAttributeKey() { - int start = pos; - while (!isEmpty() && (matchesWord() || matchesAny('-', '_', ':'))) - pos++; - - return queue.substring(start, pos); - } - /** Consume and return whatever is left on the queue. @return remained of queue. diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index bbe44ce458..8d599ce88f 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -44,16 +44,6 @@ public Cleaner(Safelist safelist) { this.safelist = safelist; } - /** - Use {@link #Cleaner(Safelist)} instead. - @deprecated as of 1.14.1. - */ - @Deprecated - public Cleaner(Whitelist whitelist) { - Validate.notNull(whitelist); - this.safelist = whitelist; - } - /** Creates a new, clean document, from the original dirty document, containing only elements allowed by the safelist. The original document is not modified. Only elements from the dirty document's <code>body</code> are used. The diff --git a/src/main/java/org/jsoup/safety/Whitelist.java b/src/main/java/org/jsoup/safety/Whitelist.java deleted file mode 100644 index 5318ae675e..0000000000 --- a/src/main/java/org/jsoup/safety/Whitelist.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.jsoup.safety; - -import org.jsoup.nodes.Attribute; -import org.jsoup.nodes.Attributes; -import org.jsoup.nodes.Element; - -/** - @deprecated As of release <code>v1.14.1</code>, this class is deprecated in favour of {@link Safelist}. The name has - been changed with the intent of promoting more inclusive language. {@link Safelist} is a drop-in replacement, and no - further changes other than updating the name in your code are required to cleanly migrate. This class will be - removed in <code>v1.15.1</code>. Until that release, this class acts as a shim to maintain code compatibility - (source and binary). - <p> - For a clear rationale for this change, please see - <a href="https://tools.ietf.org/html/draft-knodel-terminology-04" title="draft-knodel-terminology-04">Terminology, - Power, and Inclusive Language in Internet-Drafts and RFCs</a> */ -@Deprecated -public class Whitelist extends Safelist { - public Whitelist() { - super(); - } - - public Whitelist(Safelist copy) { - super(copy); - } - - static public Whitelist basic() { - return new Whitelist(Safelist.basic()); - } - - static public Whitelist basicWithImages() { - return new Whitelist(Safelist.basicWithImages()); - } - - static public Whitelist none() { - return new Whitelist(Safelist.none()); - } - - static public Whitelist relaxed() { - return new Whitelist(Safelist.relaxed()); - } - - static public Whitelist simpleText() { - return new Whitelist(Safelist.simpleText()); - } - - @Override - public Whitelist addTags(String... tags) { - super.addTags(tags); - return this; - } - - @Override - public Whitelist removeTags(String... tags) { - super.removeTags(tags); - return this; - } - - @Override - public Whitelist addAttributes(String tag, String... attributes) { - super.addAttributes(tag, attributes); - return this; - } - - @Override - public Whitelist removeAttributes(String tag, String... attributes) { - super.removeAttributes(tag, attributes); - return this; - } - - @Override - public Whitelist addEnforcedAttribute(String tag, String attribute, String value) { - super.addEnforcedAttribute(tag, attribute, value); - return this; - } - - @Override - public Whitelist removeEnforcedAttribute(String tag, String attribute) { - super.removeEnforcedAttribute(tag, attribute); - return this; - } - - @Override - public Whitelist preserveRelativeLinks(boolean preserve) { - super.preserveRelativeLinks(preserve); - return this; - } - - @Override - public Whitelist addProtocols(String tag, String attribute, String... protocols) { - super.addProtocols(tag, attribute, protocols); - return this; - } - - @Override - public Whitelist removeProtocols(String tag, String attribute, String... removeProtocols) { - super.removeProtocols(tag, attribute, removeProtocols); - return this; - } - - @Override - protected boolean isSafeTag(String tag) { - return super.isSafeTag(tag); - } - - @Override - protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) { - return super.isSafeAttribute(tagName, el, attr); - } - - @Override - Attributes getEnforcedAttributes(String tagName) { - return super.getEnforcedAttributes(tagName); - } -} diff --git a/src/test/java/org/jsoup/safety/CompatibilityTests.java b/src/test/java/org/jsoup/safety/CompatibilityTests.java deleted file mode 100644 index 5d04883d5c..0000000000 --- a/src/test/java/org/jsoup/safety/CompatibilityTests.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.jsoup.safety; - -import org.jsoup.Jsoup; -import org.jsoup.MultiLocaleExtension; -import org.jsoup.TextUtil; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Entities; -import org.junit.jupiter.api.Test; - -import java.util.Locale; - -import static org.junit.jupiter.api.Assertions.*; - -/** - Tests for the deprecated {@link org.jsoup.safety.Whitelist} class source compatibility. Will be removed in - <code>v.1.15.1</code>. No net new tests here so safe to blow up. - */ -public class CompatibilityTests { - @Test - public void resolvesRelativeLinks() { - String html = "<a href='/foo'>Link</a><img src='/bar'>"; - String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages()); - assertEquals("<a href=\"http://example.com/foo\" rel=\"nofollow\">Link</a>\n<img src=\"http://example.com/bar\">", clean); - } - - @Test - public void testDropsUnknownTags() { - String h = "<p><custom foo=true>Test</custom></p>"; - String cleanHtml = Jsoup.clean(h, Whitelist.relaxed()); - assertEquals("<p>Test</p>", cleanHtml); - } - - @Test - public void preservesRelativeLinksIfConfigured() { - String html = "<a href='/foo'>Link</a><img src='/bar'> <img src='javascript:alert()'>"; - String clean = Jsoup.clean(html, "http://example.com/", Whitelist.basicWithImages().preserveRelativeLinks(true)); - assertEquals("<a href=\"/foo\" rel=\"nofollow\">Link</a>\n<img src=\"/bar\"> \n<img>", clean); - } - - @Test - public void handlesCustomProtocols() { - String html = "<img src='cid:12345' /> <img src='data:gzzt' />"; - String dropped = Jsoup.clean(html, Whitelist.basicWithImages()); - assertEquals("<img> \n<img>", dropped); - - String preserved = Jsoup.clean(html, Whitelist.basicWithImages().addProtocols("img", "src", "cid", "data")); - assertEquals("<img src=\"cid:12345\"> \n<img src=\"data:gzzt\">", preserved); - } - - @Test - public void handlesFramesets() { - String dirty = "<html><head><script></script><noscript></noscript> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><frameset><frame src=\"foo\" /><frame src=\"foo\" /></frameset></html>"; - String clean = Jsoup.clean(dirty, Whitelist.basic()); - assertEquals("", clean); // nothing good can come out of that - - Document dirtyDoc = Jsoup.parse(dirty); - Document cleanDoc = new Cleaner(Whitelist.basic()).clean(dirtyDoc); - assertNotNull(cleanDoc); - assertEquals(0, cleanDoc.body().childNodeSize()); - } - - @Test public void handlesCleanerFromWhitelist() { - Cleaner cleaner = new Cleaner(Whitelist.basic()); - Document doc = Jsoup.parse("<script>Script</script><p>Text</p>"); - Document clean = cleaner.clean(doc); - assertEquals("<p>Text</p>", clean.body().html()); - } - - @Test - public void supplyOutputSettings() { - // test that one can override the default document output settings - Document.OutputSettings os = new Document.OutputSettings(); - os.prettyPrint(false); - os.escapeMode(Entities.EscapeMode.extended); - os.charset("ascii"); - - String html = "<div><p>&bernou;</p></div>"; - String customOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed(), os); - String defaultOut = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed()); - assertNotSame(defaultOut, customOut); - - assertEquals("<div><p>&Bscr;</p></div>", customOut); // entities now prefers shorted names if aliased - assertEquals("<div>\n" + - " <p>ℬ</p>\n" + - "</div>", defaultOut); - - os.charset("ASCII"); - os.escapeMode(Entities.EscapeMode.base); - String customOut2 = Jsoup.clean(html, "http://foo.com/", Whitelist.relaxed(), os); - assertEquals("<div><p>&#x212c;</p></div>", customOut2); - } - - @MultiLocaleExtension.MultiLocaleTest - public void safeListedProtocolShouldBeRetained(Locale locale) { - Locale.setDefault(locale); - - Whitelist safelist = Whitelist.none() - .addTags("a") - .addAttributes("a", "href") - .addProtocols("a", "href", "something"); - - String cleanHtml = Jsoup.clean("<a href=\"SOMETHING://x\"></a>", safelist); - - assertEquals("<a href=\"SOMETHING://x\"></a>", TextUtil.stripNewlines(cleanHtml)); - } -} From f16f71d6426b795de13ea9cfaf3d9c59df39dfc8 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Wed, 6 Oct 2021 22:50:39 +1100 Subject: [PATCH 671/774] Name update --- src/test/java/org/jsoup/safety/CleanerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 33380549c6..f73e462ba4 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -333,9 +333,9 @@ public void bailsIfRemovingProtocolThatsNotSet() { Document orig = Jsoup.parse("<p>test<br></p>"); orig.outputSettings().syntax(Document.OutputSettings.Syntax.xml); orig.outputSettings().escapeMode(Entities.EscapeMode.xhtml); - Safelist whitelist = Safelist.none().addTags("p", "br"); + Safelist safelist = Safelist.none().addTags("p", "br"); - Document result = new Cleaner(whitelist).clean(orig); + Document result = new Cleaner(safelist).clean(orig); assertEquals(Document.OutputSettings.Syntax.xml, result.outputSettings().syntax()); assertEquals("<p>test<br /></p>", result.body().html()); } From adba4e80be13fb7a8d0ddfb31258b628bba61b99 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Thu, 7 Oct 2021 22:08:31 +1100 Subject: [PATCH 672/774] Added :containsWholeText Part of #1636 --- CHANGES | 4 ++++ src/main/java/org/jsoup/select/Evaluator.java | 23 +++++++++++++++++++ .../java/org/jsoup/select/QueryParser.java | 9 ++++++++ src/main/java/org/jsoup/select/Selector.java | 1 + .../java/org/jsoup/select/SelectorTest.java | 20 ++++++++++++++++ 5 files changed, 57 insertions(+) diff --git a/CHANGES b/CHANGES index 61dd021e01..b108fed3fc 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ jsoup changelog input document is using the HTML syntax. (Previously, would always coerce using the more restrictive XML syntax.) <https://github.com/jhy/jsoup/pull/1648> + * Improvement: added the :containsWholeText(text) selector, to match against non-normalized Element text. That can be + useful when elements can only be distinguished by e.g. specific case, or leading whitespace, etc. + <https://github.com/jhy/jsoup/issues/1636> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 52cd432099..8c319f33bf 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -681,6 +681,29 @@ public String toString() { } } + /** + * Evaluator for matching Element (and its descendants) wholeText. Neither the input nor the element text is + * normalized. <code>:containsWholeText()</code> + * @since 1.15.1. + */ + public static final class ContainsWholeText extends Evaluator { + private final String searchText; + + public ContainsWholeText(String searchText) { + this.searchText = searchText; + } + + @Override + public boolean matches(Element root, Element element) { + return element.wholeText().contains(searchText); + } + + @Override + public String toString() { + return String.format(":containsWholeText(%s)", searchText); + } + } + /** * Evaluator for matching Element (and its descendants) data */ diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index bed123b494..46849a52fa 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -180,6 +180,8 @@ else if (tq.matches(":contains(")) contains(false); else if (tq.matches(":containsOwn(")) contains(true); + else if (tq.matches(":containsWholeText(")) + containsWholeText(); else if (tq.matches(":containsData(")) containsData(); else if (tq.matches(":matches(")) @@ -367,6 +369,13 @@ private void contains(boolean own) { evals.add(new Evaluator.ContainsText(searchText)); } + private void containsWholeText() { + tq.consume(":containsWholeText"); + String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); + Validate.notEmpty(searchText, ":containsWholeText(text) query must not be empty"); + evals.add(new Evaluator.ContainsWholeText(searchText)); + } + // pseudo selector :containsData(data) private void containsData() { tq.consume(":containsData"); diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 511efbafc2..dd86e091ac 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -53,6 +53,7 @@ * <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contains the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants. The text is whitespace normalized. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:contains(jsoup)</code> finds p elements containing the text "jsoup".<p>{@code p:contains(hello \(there\) finds p elements containing the text "Hello (There)"}</p></td></tr> * <tr><td><code>:containsOwn(<em>text</em>)</code></td><td>elements that directly contain the specified text. The search is case insensitive. The text must appear in the found element, not any of its descendants.</td><td><code>p:containsOwn(jsoup)</code> finds p elements with own text "jsoup".</td></tr> * <tr><td><code>:containsData(<em>data</em>)</code></td><td>elements that contains the specified <em>data</em>. The contents of {@code script} and {@code style} elements, and {@code comment} nodes (etc) are considered data nodes, not text nodes. The search is case insensitive. The data may appear in the found element, or any of its descendants.</td><td><code>script:contains(jsoup)</code> finds script elements containing the data "jsoup".</td></tr> + * <tr><td><code>:containsWholeText(<em>text</em>)</code></td><td>elements that contains the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, or any of its descendants. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:containsWholeText(jsoup\nThe Java HTML Parser)</code> finds p elements containing the text <code>"jsoup\nThe Java HTML Parser"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would.</p></td></tr> * <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements containing <b>whitespace normalized</b> text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> * <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesOwn(\\d+)</code> finds table cells directly containing digits. <code>div:matchesOwn((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> * <tr><td></td><td>The above may be combined in any order and with other selectors</td><td><code>.light:contains(name):eq(0)</code></td></tr> diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index bedb41440b..14d50431ca 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -617,6 +617,26 @@ public void testPseudoContains(Locale locale) { assertEquals("2", ps2.first().id()); } + @Test void containsWholeText() { + Document doc = Jsoup.parse("<div><p> jsoup\n The <i>HTML</i> Parser</p><p>jsoup The HTML Parser</div>"); + Elements ps = doc.select("p"); + + Elements es1 = doc.select("p:containsWholeText( jsoup\n The HTML Parser)"); + Elements es2 = doc.select("p:containsWholeText(jsoup The HTML Parser)"); + assertEquals(1, es1.size()); + assertEquals(1, es2.size()); + assertEquals(ps.get(0), es1.first()); + assertEquals(ps.get(1), es2.first()); + + assertEquals(0, doc.select("div:containsWholeText(jsoup the html parser)").size()); + assertEquals(0, doc.select("div:containsWholeText(jsoup\n the html parser)").size()); + + doc = Jsoup.parse("<div><p></p><p> </p><p>. </p>"); + Elements blanks = doc.select("p:containsWholeText( )"); + assertEquals(1, blanks.size()); + assertEquals(". ", blanks.first().wholeText()); + } + @MultiLocaleTest public void containsOwn(Locale locale) { Locale.setDefault(locale); From 7f28cb0e9cc02809ec12cbc0b0ba85a4f07e312f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Oct 2021 16:59:38 +1100 Subject: [PATCH 673/774] Bump japicmp-maven-plugin from 0.15.3 to 0.15.4 (#1651) Bumps [japicmp-maven-plugin](https://github.com/siom79/japicmp) from 0.15.3 to 0.15.4. - [Release notes](https://github.com/siom79/japicmp/releases) - [Changelog](https://github.com/siom79/japicmp/blob/master/release.py) - [Commits](https://github.com/siom79/japicmp/compare/japicmp-base-0.15.3...japicmp-base-0.15.4) --- updated-dependencies: - dependency-name: com.github.siom79.japicmp:japicmp-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0b0b30e32..fdc33d6dfb 100644 --- a/pom.xml +++ b/pom.xml @@ -192,7 +192,7 @@ <!-- API version compat check - https://siom79.github.io/japicmp/ --> <groupId>com.github.siom79.japicmp</groupId> <artifactId>japicmp-maven-plugin</artifactId> - <version>0.15.3</version> + <version>0.15.4</version> <configuration> <!-- hard code previous version; can't detect when running stateless on build server --> <oldVersion> From 1e4d12797caa0cd53e140fe32b4fe0a21b07eb30 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 19 Oct 2021 18:14:07 +1100 Subject: [PATCH 674/774] When evaluating XPath on a context node, use the entire w3c document And track the context in user data sections. This allows queries to evaluate against the complete document, vs just a sub-tree. Fixes #1652 --- CHANGES | 5 ++ src/main/java/org/jsoup/helper/W3CDom.java | 64 +++++++++++++++---- src/main/java/org/jsoup/nodes/NodeUtils.java | 3 +- .../java/org/jsoup/helper/W3CDomTest.java | 2 +- src/test/java/org/jsoup/select/XpathTest.java | 31 ++++++++- 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index b108fed3fc..1bb4842593 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,11 @@ jsoup changelog useful when elements can only be distinguished by e.g. specific case, or leading whitespace, etc. <https://github.com/jhy/jsoup/issues/1636> + * Improvement: when evaluating an XPath query against a context element, the complete document is now visible to the + query, vs only the context element's sub-tree. This enables support for queries outside (parent or sibling) the + element, e.g. ancestor-or-self::*. + <https://github.com/jhy/jsoup/issues/1652> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 3b0b5df692..753c91265f 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -49,6 +49,9 @@ public class W3CDom { /** For W3C Documents created by this class, this property is set on each node to link back to the original jsoup node. */ public static final String SourceProperty = "jsoupSource"; + private static final String ContextProperty = "jsoupContextSource"; // tracks the jsoup context element on w3c doc + private static final String ContextNodeProperty = "jsoupContextNode"; // the w3c node used as the creating context + /** To get support for XPath versions &gt; 1, set this property to the classname of an alternate XPathFactory @@ -161,12 +164,15 @@ public Document fromJsoup(org.jsoup.nodes.Document in) { } /** - * Convert a jsoup Element to a W3C Document. The created nodes will link back to the original + * Convert a jsoup DOM to a W3C Document. The created nodes will link back to the original * jsoup nodes in the user property {@link #SourceProperty} (but after conversion, changes on one side will not - * flow to the other). + * flow to the other). The input Element is used as a context node, but the whole surrounding jsoup Document is + * converted. (If you just want a subtree converted, use {@link #convert(org.jsoup.nodes.Element, Document)}.) * * @param in jsoup element or doc * @return a W3C DOM Document representing the jsoup Document or Element contents. + * @see #sourceNodes(NodeList, Class) + * @see #contextNode(Document) */ public Document fromJsoup(org.jsoup.nodes.Element in) { Validate.notNull(in); @@ -174,9 +180,7 @@ public Document fromJsoup(org.jsoup.nodes.Element in) { try { builder = factory.newDocumentBuilder(); DOMImplementation impl = builder.getDOMImplementation(); - Document out; - - out = builder.newDocument(); + Document out = builder.newDocument(); org.jsoup.nodes.Document inDoc = in.ownerDocument(); org.jsoup.nodes.DocumentType doctype = inDoc != null ? inDoc.documentType() : null; if (doctype != null) { @@ -184,8 +188,10 @@ public Document fromJsoup(org.jsoup.nodes.Element in) { out.appendChild(documentType); } out.setXmlStandalone(true); - - convert(in, out); + // if in is Document, use the root element, not the wrapping document, as the context: + org.jsoup.nodes.Element context = (in instanceof org.jsoup.nodes.Document) ? in.child(0) : in; + out.setUserData(ContextProperty, context, null); + convert(inDoc != null ? inDoc : in, out); return out; } catch (ParserConfigurationException e) { throw new IllegalStateException(e); @@ -226,9 +232,25 @@ public void convert(org.jsoup.nodes.Element in, Document out) { NodeTraversor.traverse(builder, rootEl); } + /** + Evaluate an XPath query against the supplied document, and return the results. + @param xpath an XPath query + @param doc the document to evaluate against + @return the matches nodes + */ public NodeList selectXpath(String xpath, Document doc) { + return selectXpath(xpath, (Node) doc); + } + + /** + Evaluate an XPath query against the supplied context node, and return the results. + @param xpath an XPath query + @param contextNode the context node to evaluate against + @return the matches nodes + */ + public NodeList selectXpath(String xpath, Node contextNode) { Validate.notEmpty(xpath); - Validate.notNull(doc); + Validate.notNull(contextNode); NodeList nodeList; try { @@ -239,7 +261,7 @@ public NodeList selectXpath(String xpath, Document doc) { XPathFactory.newInstance(); XPathExpression expression = xPathFactory.newXPath().compile(xpath); - nodeList = (NodeList) expression.evaluate(doc, XPathConstants.NODESET); // love the strong typing here /s + nodeList = (NodeList) expression.evaluate(contextNode, XPathConstants.NODESET); // love the strong typing here /s Validate.notNull(nodeList); } catch (XPathExpressionException | XPathFactoryConfigurationException e) { throw new Selector.SelectorParseException("Could not evaluate XPath query [%s]: %s", xpath, e.getMessage()); @@ -247,6 +269,13 @@ public NodeList selectXpath(String xpath, Document doc) { return nodeList; } + /** + Retrieves the original jsoup DOM nodes from a nodelist created by this convertor. + @param nodeList the W3C nodes to get the original jsoup nodes from + @param nodeType the jsoup node type to retrieve (e.g. Element, DataNode, etc) + @param <T> node type + @return a list of the original nodes + */ public <T extends org.jsoup.nodes.Node> List<T> sourceNodes(NodeList nodeList, Class<T> nodeType) { Validate.notNull(nodeList); Validate.notNull(nodeType); @@ -262,6 +291,15 @@ public <T extends org.jsoup.nodes.Node> List<T> sourceNodes(NodeList nodeList, C return nodes; } + /** + For a Document created by {@link #fromJsoup(org.jsoup.nodes.Element)}, retrieves the W3C context node. + @param wDoc Document created by this class + @return the corresponding W3C Node to the jsoup Element that was used as the creating context. + */ + public Node contextNode(Document wDoc) { + return (Node) wDoc.getUserData(ContextNodeProperty); + } + /** * Serialize a W3C document to a String. The output format will be XML or HTML depending on the content of the doc. * @@ -284,11 +322,13 @@ protected static class W3CBuilder implements NodeVisitor { private final Stack<HashMap<String, String>> namespacesStack = new Stack<>(); // stack of namespaces, prefix => urn private Node dest; private Syntax syntax = Syntax.xml; // the syntax (to coerce attributes to). From the input doc if available. + @Nullable private final org.jsoup.nodes.Element contextElement; public W3CBuilder(Document doc) { this.doc = doc; - this.namespacesStack.push(new HashMap<>()); - this.dest = doc; + namespacesStack.push(new HashMap<>()); + dest = doc; + contextElement = (org.jsoup.nodes.Element) doc.getUserData(ContextProperty); // Track the context jsoup Element, so we can save the corresponding w3c element } public void head(org.jsoup.nodes.Node source, int depth) { @@ -310,6 +350,8 @@ public void head(org.jsoup.nodes.Node source, int depth) { doc.createElementNS(namespace, tagName); copyAttributes(sourceEl, el); append(el, sourceEl); + if (sourceEl == contextElement) + doc.setUserData(ContextNodeProperty, el, null); dest = el; // descend } catch (DOMException e) { append(doc.createTextNode("<" + tagName + ">"), sourceEl); diff --git a/src/main/java/org/jsoup/nodes/NodeUtils.java b/src/main/java/org/jsoup/nodes/NodeUtils.java index ea6f08159f..722a614f1b 100644 --- a/src/main/java/org/jsoup/nodes/NodeUtils.java +++ b/src/main/java/org/jsoup/nodes/NodeUtils.java @@ -44,7 +44,8 @@ static <T extends Node> List<T> selectXpath(String xpath, Element el, Class<T> n W3CDom w3c = new W3CDom(); org.w3c.dom.Document wDoc = w3c.fromJsoup(el); - NodeList nodeList = w3c.selectXpath(xpath, wDoc); + org.w3c.dom.Node contextNode = w3c.contextNode(wDoc); + NodeList nodeList = w3c.selectXpath(xpath, contextNode); return w3c.sourceNodes(nodeList, nodeType); } } diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index f1ff904454..09fc66f352 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -328,7 +328,7 @@ private void assertEqualsIgnoreCase(String want, String have) { Element jDiv = jdoc.selectFirst("div"); assertNotNull(jDiv); Document doc = w3CDom.fromJsoup(jDiv); - Node div = doc.getFirstChild(); + Node div = w3CDom.contextNode(doc); assertEquals("div", div.getLocalName()); assertEquals(jDiv, div.getUserData(W3CDom.SourceProperty)); diff --git a/src/test/java/org/jsoup/select/XpathTest.java b/src/test/java/org/jsoup/select/XpathTest.java index e46f17978c..2b3393a2ce 100644 --- a/src/test/java/org/jsoup/select/XpathTest.java +++ b/src/test/java/org/jsoup/select/XpathTest.java @@ -43,13 +43,15 @@ public void supportsXpath() { Element div = doc.selectFirst("div"); assertNotNull(div); + Element w3cDiv = div.selectXpath(".").first(); // self + assertSame(div, w3cDiv); - Elements els = div.selectXpath("/div/p"); + Elements els = div.selectXpath("p"); assertEquals(1, els.size()); assertEquals("One", els.get(0).text()); assertEquals("p", els.get(0).tagName()); - assertEquals(0, div.selectXpath("//body").size()); + assertEquals(1, div.selectXpath("//body").size()); // the whole document is visible on the div context assertEquals(1, doc.selectXpath("//body").size()); } @@ -146,6 +148,31 @@ private static Stream<Arguments> provideEvaluators() { assertEquals("/bar", hrefs.get(1)); } + @Test void selectOutsideOfElementTree() { + Document doc = Jsoup.parse("<p>One<p>Two<p>Three"); + Elements ps = doc.selectXpath("//p"); + assertEquals(3, ps.size()); + + Element p1 = ps.get(0); + assertEquals("One", p1.text()); + + Elements sibs = p1.selectXpath("following-sibling::p"); + assertEquals(2, sibs.size()); + assertEquals("Two", sibs.get(0).text()); + assertEquals("Three", sibs.get(1).text()); + } + + @Test void selectAncestorsOnContextElement() { + // https://github.com/jhy/jsoup/issues/1652 + Document doc = Jsoup.parse("<div><p>Hello"); + Element p = doc.selectFirst("p"); + assertNotNull(p); + Elements chain = p.selectXpath("ancestor-or-self::*"); + assertEquals(4, chain.size()); + assertEquals("html", chain.get(0).tagName()); + assertEquals("p", chain.get(3).tagName()); + } + @Test public void canSupplyAlternateFactoryImpl() { // previously we had a test to load Saxon and do an XPath 2.0 query. But we know Saxon works and so that's From 7eb5a745bd44b26777d99e429bd5036597aa6273 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 20 Oct 2021 15:39:37 +1100 Subject: [PATCH 675/774] Normalize attribute names when testing if a boolean attribute Fixes #1656 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Attribute.java | 3 ++- .../java/org/jsoup/nodes/AttributeTest.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1bb4842593..eda0d2bc46 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,10 @@ jsoup changelog element, e.g. ancestor-or-self::*. <https://github.com/jhy/jsoup/issues/1652> + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve + case. + <https://github.com/jhy/jsoup/issues/1656> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index a1b2ae21ec..5cdecc339c 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -2,6 +2,7 @@ import org.jsoup.SerializationException; import org.jsoup.helper.Validate; +import org.jsoup.internal.Normalizer; import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Document.OutputSettings.Syntax; @@ -211,7 +212,7 @@ protected static boolean shouldCollapseAttribute(final String key, @Nullable fin * Checks if this attribute name is defined as a boolean attribute in HTML5 */ public static boolean isBooleanAttribute(final String key) { - return Arrays.binarySearch(booleanAttributes, key) >= 0; + return Arrays.binarySearch(booleanAttributes, Normalizer.lowerCase(key)) >= 0; } @Override diff --git a/src/test/java/org/jsoup/nodes/AttributeTest.java b/src/test/java/org/jsoup/nodes/AttributeTest.java index 629ed8bf64..454b3bb041 100644 --- a/src/test/java/org/jsoup/nodes/AttributeTest.java +++ b/src/test/java/org/jsoup/nodes/AttributeTest.java @@ -1,6 +1,8 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; +import org.jsoup.parser.ParseSettings; +import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -72,4 +74,19 @@ public void html() { oldVal = attr.setValue("foo"); assertEquals("", oldVal); // string, not null } + + @Test void booleanAttributesAreNotCaseSensitive() { + // https://github.com/jhy/jsoup/issues/1656 + assertTrue(Attribute.isBooleanAttribute("required")); + assertTrue(Attribute.isBooleanAttribute("REQUIRED")); + assertTrue(Attribute.isBooleanAttribute("rEQUIREd")); + assertFalse(Attribute.isBooleanAttribute("random string")); + + String html = "<a href=autofocus REQUIRED>One</a>"; + Document doc = Jsoup.parse(html); + assertEquals("<a href=\"autofocus\" required>One</a>", doc.selectFirst("a").outerHtml()); + + Document doc2 = Jsoup.parse(html, Parser.htmlParser().settings(ParseSettings.preserveCase)); + assertEquals("<a href=\"autofocus\" REQUIRED>One</a>", doc2.selectFirst("a").outerHtml()); + } } From 3bd4d793ad7af94b3596fdf35be86758ff94696b Mon Sep 17 00:00:00 2001 From: Jeremy Landis <jeremylandis@hotmail.com> Date: Wed, 20 Oct 2021 22:50:36 -0400 Subject: [PATCH 676/774] Expose maxPaddingWidth in OutputSettings keeping default as 30 (#1655) Co-authored-by: Jonathan Hedley <jonathan@hedley.net> --- CHANGES | 4 +++ .../java/org/jsoup/internal/StringUtil.java | 26 ++++++++++----- src/main/java/org/jsoup/nodes/Document.java | 24 +++++++++++++- src/main/java/org/jsoup/nodes/Node.java | 2 +- .../org/jsoup/internal/StringUtilTest.java | 29 +++++++++++++++- .../java/org/jsoup/nodes/ElementTest.java | 33 +++++++++++++++++++ 6 files changed, 107 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index eda0d2bc46..45bffa40fb 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,10 @@ jsoup changelog element, e.g. ancestor-or-self::*. <https://github.com/jhy/jsoup/issues/1652> + * Improvement: allow a maxPaddingWidth on the indent level in OutputSettings when pretty printing. This defaults to + 30 to limit the indent level for very deeply nested elements, and may be disabled by setting to -1. + <https://github.com/jhy/jsoup/pull/1655> + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. <https://github.com/jhy/jsoup/issues/1656> diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index eed2422062..1882f2a9f1 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -16,11 +16,10 @@ notice. */ public final class StringUtil { - // memoised padding up to 21 + // memoised padding up to 21 (blocks 0 to 20 spaces) static final String[] padding = {"", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}; - private static final int maxPaddingWidth = 30; // so very deeply nested nodes don't get insane padding amounts /** * Join a collection of strings by a separator @@ -115,17 +114,28 @@ public String complete() { } /** - * Returns space padding (up to a max of 30). + * Returns space padding (up to the default max of 30). Use {@link #padding(int, int)} to specify a different limit. * @param width amount of padding desired * @return string of spaces * width - */ + * @see #padding(int, int) + */ public static String padding(int width) { - if (width < 0) - throw new IllegalArgumentException("width must be > 0"); + return padding(width, 30); + } + /** + * Returns space padding, up to a max of maxPaddingWidth. + * @param width amount of padding desired + * @param maxPaddingWidth maximum padding to apply. Set to {@code -1} for unlimited. + * @return string of spaces * width + */ + public static String padding(int width, int maxPaddingWidth) { + Validate.isTrue(width >= 0, "width must be >= 0"); + Validate.isTrue(maxPaddingWidth >= -1); + if (maxPaddingWidth != -1) + width = Math.min(width, maxPaddingWidth); if (width < padding.length) - return padding[width]; - width = Math.min(width, maxPaddingWidth); + return padding[width]; char[] out = new char[width]; for (int i = 0; i < width; i++) out[i] = ' '; diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 1e1217bba7..6e5d8e3202 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -412,6 +412,7 @@ public enum Syntax {html, xml} private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; + private int maxPaddingWidth = 30; private Syntax syntax = Syntax.html; public OutputSettings() {} @@ -560,6 +561,27 @@ public OutputSettings indentAmount(int indentAmount) { return this; } + /** + * Get the current max padding amount, used when pretty printing + * so very deeply nested nodes don't get insane padding amounts. + * @return the current indent amount + */ + public int maxPaddingWidth() { + return maxPaddingWidth; + } + + /** + * Set the max padding amount for pretty printing so very deeply nested nodes don't get insane padding amounts. + * @param maxPaddingWidth number of spaces to use for indenting each level of nested nodes. Must be {@literal >=} -1. + * Default is 30 and -1 means unlimited. + * @return this, for chaining + */ + public OutputSettings maxPaddingWidth(int maxPaddingWidth) { + Validate.isTrue(maxPaddingWidth >= -1); + this.maxPaddingWidth = maxPaddingWidth; + return this; + } + @Override public OutputSettings clone() { OutputSettings clone; @@ -570,7 +592,7 @@ public OutputSettings clone() { } clone.charset(charset.name()); // new charset and charset encoder clone.escapeMode = Entities.EscapeMode.valueOf(escapeMode.name()); - // indentAmount, prettyPrint are primitives so object.clone() will handle + // indentAmount, maxPaddingWidth, and prettyPrint are primitives so object.clone() will handle return clone; } } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 5d62478408..27886f46b0 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -683,7 +683,7 @@ public String toString() { } protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException { - accum.append('\n').append(StringUtil.padding(depth * out.indentAmount())); + accum.append('\n').append(StringUtil.padding(depth * out.indentAmount(), out.maxPaddingWidth())); } /** diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index 1956084bae..2f4fff5da0 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -24,7 +24,34 @@ public void join() { assertEquals(" ", StringUtil.padding(1)); assertEquals(" ", StringUtil.padding(2)); assertEquals(" ", StringUtil.padding(15)); - assertEquals(" ", StringUtil.padding(45)); // we tap out at 30 + assertEquals(" ", StringUtil.padding(45)); // we default to tap out at 30 + + // memoization is up to 21 blocks (0 to 20 spaces) and exits early before min checks making maxPaddingWidth unused + assertEquals("", StringUtil.padding(0, -1)); + assertEquals(" ", StringUtil.padding(20, -1)); + + // this test escapes memoization and continues through + assertEquals(" ", StringUtil.padding(21, -1)); + + // this test escapes memoization and using unlimited length (-1) will allow requested spaces + assertEquals(" ", StringUtil.padding(30, -1)); + assertEquals(" ", StringUtil.padding(45, -1)); + + // we tap out at 0 for this test + assertEquals("", StringUtil.padding(0, 0)); + + // as memoization is escaped, setting zero for max padding will not allow any requested width + assertEquals("", StringUtil.padding(21, 0)); + + // we tap out at 30 for these tests making > 30 use 30 + assertEquals("", StringUtil.padding(0, 30)); + assertEquals(" ", StringUtil.padding(1, 30)); + assertEquals(" ", StringUtil.padding(2, 30)); + assertEquals(" ", StringUtil.padding(15, 30)); + assertEquals(" ", StringUtil.padding(45, 30)); + + // max applies regardless of memoized + assertEquals(5, StringUtil.padding(20, 5).length()); } @Test public void paddingInACan() { diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 52afb83092..563442bd04 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; @@ -430,6 +431,38 @@ public void testSetIndent() { assertEquals("<html>\n<head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<div>\n<p>Hello there</p>\n</div>\n</body>\n</html>", doc.html()); } + @Test void testIndentLevel() { + // deep to test default and extended max + StringBuilder divs = new StringBuilder(); + for (int i = 0; i < 40; i++) { + divs.append("<div>"); + } + divs.append("Foo"); + Document doc = Jsoup.parse(divs.toString()); + Document.OutputSettings settings = doc.outputSettings(); + + int defaultMax = 30; + assertEquals(defaultMax, settings.maxPaddingWidth()); + String html = doc.html(); + assertTrue(html.contains(" <div>\n" + + " Foo\n" + + " </div>")); + + settings.maxPaddingWidth(32); + assertEquals(32, settings.maxPaddingWidth()); + html = doc.html(); + assertTrue(html.contains(" <div>\n" + + " Foo\n" + + " </div>")); + + settings.maxPaddingWidth(-1); + assertEquals(-1, settings.maxPaddingWidth()); + html = doc.html(); + assertTrue(html.contains(" <div>\n" + + " Foo\n" + + " </div>")); + } + @Test public void testNotPretty() { Document doc = Jsoup.parse("<div> \n<p>Hello\n there\n</p></div>"); From 801b221db98bd19e9e95b696f131ad15e757edea Mon Sep 17 00:00:00 2001 From: Jeremy Landis <jeremylandis@hotmail.com> Date: Tue, 2 Nov 2021 00:40:00 -0400 Subject: [PATCH 677/774] Bump jetty-servlet to 9.4.44.v20210927 (#1659) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fdc33d6dfb..f41ad9cf51 100644 --- a/pom.xml +++ b/pom.xml @@ -328,7 +328,7 @@ <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.43.v20210629</version> + <version>9.4.44.v20210927</version> <scope>test</scope> </dependency> From 46b0b9569a76b65f31e75fa94d38f6766709c52a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:41:45 +1100 Subject: [PATCH 678/774] Bump gson from 2.8.8 to 2.8.9 (#1660) Bumps [gson](https://github.com/google/gson) from 2.8.8 to 2.8.9. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.8.8...gson-parent-2.8.9) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f41ad9cf51..41def9376c 100644 --- a/pom.xml +++ b/pom.xml @@ -312,7 +312,7 @@ <!-- gson, to fetch entities from w3.org --> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>2.8.8</version> + <version>2.8.9</version> <scope>test</scope> </dependency> From 7c9539cc7a5780f3ee3bfffcbb3a09fc6c499394 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 19 Dec 2021 14:52:57 +1100 Subject: [PATCH 679/774] Fixed comment transition and removed some parse errors Fixes #1667 --- CHANGES | 3 +++ src/main/java/org/jsoup/parser/TokeniserState.java | 5 +---- src/test/java/org/jsoup/parser/HtmlParserTest.java | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 45bffa40fb..49fb0edf4f 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,9 @@ jsoup changelog case. <https://github.com/jhy/jsoup/issues/1656> + * Bugfix: a comment with all dashes (<!----->) should not emit a parse error. + <https://github.com/jhy/jsoup/issues/1667> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/parser/TokeniserState.java b/src/main/java/org/jsoup/parser/TokeniserState.java index acc8db35b3..874fed0c05 100644 --- a/src/main/java/org/jsoup/parser/TokeniserState.java +++ b/src/main/java/org/jsoup/parser/TokeniserState.java @@ -970,7 +970,7 @@ void read(Tokeniser t, CharacterReader r) { char c = r.consume(); switch (c) { case '-': - t.transition(CommentStartDash); + t.transition(CommentEnd); break; case nullChar: t.error(this); @@ -1052,11 +1052,9 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Comment); break; case '!': - t.error(this); t.transition(CommentEndBang); break; case '-': - t.error(this); t.commentPending.append('-'); break; case eof: @@ -1065,7 +1063,6 @@ void read(Tokeniser t, CharacterReader r) { t.transition(Data); break; default: - t.error(this); t.commentPending.append("--").append(c); t.transition(Comment); } diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 78d7ca320a..08accbbd35 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -102,6 +102,17 @@ public class HtmlParserTest { assertEquals(" <tr><td>", comment.getData()); } + @Test void allDashCommentsAreNotParseErrors() { + // https://github.com/jhy/jsoup/issues/1667 + // <!-----> is not a parse error + String html = "<!------>"; + Parser parser = Parser.htmlParser().setTrackErrors(10); + Document doc = Jsoup.parse(html, parser); + Comment comment = (Comment) doc.childNode(0); + assertEquals("--", comment.getData()); + assertEquals(0, parser.getErrors().size()); + } + @Test public void dropsUnterminatedTag() { // jsoup used to parse this to <p>, but whatwg, webkit will drop. String h1 = "<p"; From b049d5bc2a95fbdd4f7eb0be2bfa2dc36643bfe3 Mon Sep 17 00:00:00 2001 From: KirylBubovich <47569841+KirylBubovich@users.noreply.github.com> Date: Sun, 19 Dec 2021 07:21:57 +0300 Subject: [PATCH 680/774] Fix sequence input stream parsing (#1671) Fix sequence input stream parsing Co-authored-by: KirylBubovich <kiryl.bubovich@duallab.com> --- CHANGES | 4 ++++ .../internal/ConstrainableInputStream.java | 4 ++-- .../java/org/jsoup/helper/DataUtilTest.java | 17 +++++++++++++++++ src/test/resources/htmltests/medium.html | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/htmltests/medium.html diff --git a/CHANGES b/CHANGES index 49fb0edf4f..5e76751144 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,10 @@ jsoup changelog case. <https://github.com/jhy/jsoup/issues/1656> + * Bugfix: when reading from SequenceInputStreams across the buffer, the input stream was closed too early, resulting + in missed content. + <https://github.com/jhy/jsoup/pull/1671> + * Bugfix: a comment with all dashes (<!----->) should not emit a parse error. <https://github.com/jhy/jsoup/issues/1667> diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index c043b421da..0f7e3c3b88 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -81,10 +81,10 @@ public ByteBuffer readToByteBuffer(int max) throws IOException { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize); int read; - int remaining = max; + int remaining = bufferSize; while (true) { - read = read(readBuffer); + read = read(readBuffer, 0, remaining); if (read == -1) break; if (localCapped) { // this local byteBuffer cap may be smaller than the overall maxSize (like when reading first bytes) if (read >= remaining) { diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index a775723fa0..59027aeb52 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -7,6 +7,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import static org.jsoup.integration.ParseTest.getFile; import static org.junit.jupiter.api.Assertions.*; @@ -130,6 +131,22 @@ public void firstMetaElementWithCharsetShouldBeUsedForDecoding() throws Exceptio assertEquals("Übergrößenträger", doc.body().text()); } + @Test + public void parseSequenceInputStream() throws IOException { + // https://github.com/jhy/jsoup/pull/1671 + File in = getFile("/htmltests/medium.html"); + String fileContent = new String(Files.readAllBytes(in.toPath())); + int halfLength = fileContent.length() / 2; + String firstPart = fileContent.substring(0, halfLength); + String secondPart = fileContent.substring(halfLength); + SequenceInputStream sequenceStream = new SequenceInputStream( + stream(firstPart), + stream(secondPart) + ); + Document doc = DataUtil.parseInputStream(sequenceStream, null, "", Parser.htmlParser()); + assertEquals(fileContent, doc.outerHtml()); + } + @Test public void supportsBOMinFiles() throws IOException { // test files from http://www.i18nl10n.com/korean/utftest/ diff --git a/src/test/resources/htmltests/medium.html b/src/test/resources/htmltests/medium.html new file mode 100644 index 0000000000..10f4402ffc --- /dev/null +++ b/src/test/resources/htmltests/medium.html @@ -0,0 +1,18 @@ +<html> + <head> + <title>Large HTML</title> + <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> + <body> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + <p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p> + <p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> + <p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> + <p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> + <p><i>Proin sodales libero eget ante</i>. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> + <p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> + <p>Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Suspendisse potenti</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> + <p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> + <p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + </body> +</html> \ No newline at end of file From 81c697b5445c4b3706cd7bc2b495d3faf9092d5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:49:25 +1100 Subject: [PATCH 681/774] Bump junit-jupiter from 5.8.1 to 5.8.2 (#1678) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.8.1 to 5.8.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.8.1...r5.8.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 41def9376c..a9c3f95191 100644 --- a/pom.xml +++ b/pom.xml @@ -304,7 +304,7 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> - <version>5.8.1</version> + <version>5.8.2</version> <scope>test</scope> </dependency> From bdd55ba64ddef1b1b918acc941c6c0ab73f54932 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:49:58 +1100 Subject: [PATCH 682/774] Bump maven-bundle-plugin from 5.1.2 to 5.1.3 (#1684) Bumps maven-bundle-plugin from 5.1.2 to 5.1.3. --- updated-dependencies: - dependency-name: org.apache.felix:maven-bundle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a9c3f95191..2ab8666367 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> - <version>5.1.2</version> + <version>5.1.3</version> <executions> <execution> <id>bundle-manifest</id> From d1e333c4f15ac01f41c2248a214e25224a8145f4 Mon Sep 17 00:00:00 2001 From: Andrej Fink <aprpda@gmail.com> Date: Sun, 19 Dec 2021 06:26:46 +0100 Subject: [PATCH 683/774] Uplift insertNode and popStackToClose to protected, so they can be used in other implementations --- src/main/java/org/jsoup/parser/XmlTreeBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index 97ef372c37..a3dc7917b5 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -78,7 +78,7 @@ protected boolean process(Token token) { return true; } - private void insertNode(Node node) { + protected void insertNode(Node node) { currentElement().appendChild(node); } @@ -129,7 +129,7 @@ void insert(Token.Doctype d) { * * @param endTag tag to close */ - private void popStackToClose(Token.EndTag endTag) { + protected void popStackToClose(Token.EndTag endTag) { // like in HtmlTreeBuilder - don't scan up forever for very (artificially) deeply nested stacks String elName = settings.normalizeTag(endTag.tagName); Element firstFound = null; From c9081fc63c9eb07530fc3e3814bb90e28da3f146 Mon Sep 17 00:00:00 2001 From: DavidKorczynski <david@adalogics.com> Date: Sun, 19 Dec 2021 09:50:44 +0000 Subject: [PATCH 684/774] Add CIFuzz integration to GitHub Actions (#1676) --- .github/workflows/cifuzz.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000000..3d265ab557 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,26 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'jsoup' + dry-run: false + language: jvm + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'jsoup' + fuzz-seconds: 600 + dry-run: false + language: jvm + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From d4f7d7ab0b25f20ec77e074bc5d47b3ee5c81f22 Mon Sep 17 00:00:00 2001 From: Jeremy Landis <jeremylandis@hotmail.com> Date: Sun, 19 Dec 2021 17:24:21 -0500 Subject: [PATCH 685/774] Remove jdk 16 from github actions as end of life (#1690) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec9c652c45..bd64c75278 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time - java: [8, 11, 16, 17] + java: [8, 11, 17] fail-fast: false name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: From 7f1732a50ae9478496f74307fc0ac22044a504d0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 11:12:22 +1100 Subject: [PATCH 686/774] Don't String.format the message unless there are formatting parameters Fixes #1691 --- CHANGES | 4 ++++ src/main/java/org/jsoup/select/QueryParser.java | 2 +- src/main/java/org/jsoup/select/Selector.java | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5e76751144..f72c4a4e16 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,10 @@ jsoup changelog * Bugfix: a comment with all dashes (<!----->) should not emit a parse error. <https://github.com/jhy/jsoup/issues/1667> + * Bugfix: when throwing a SelectorParseException for an invalid selector, don't try to String.format the input, as + that could throw an IllegalFormatException. + <https://github.com/jhy/jsoup/issues/1691> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 46849a52fa..4611624151 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -130,7 +130,7 @@ private void combinator(char combinator) { currentEval = or; break; default: - throw new Selector.SelectorParseException("Unknown combinator: " + combinator); + throw new Selector.SelectorParseException("Unknown combinator '%s'", combinator); } if (replaceRightMost) diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index dd86e091ac..8e86bf8370 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -164,6 +164,10 @@ static Elements filterOut(Collection<Element> elements, Collection<Element> outs } public static class SelectorParseException extends IllegalStateException { + public SelectorParseException(String msg) { + super(msg); + } + public SelectorParseException(String msg, Object... params) { super(String.format(msg, params)); } From 639900f6210ecd201d607d1865868dcb03a7c70d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 13:46:57 +1100 Subject: [PATCH 687/774] Exception testcase for formatter For #1691 --- src/test/java/org/jsoup/select/SelectorTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 14d50431ca..8adff3a45e 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1048,4 +1048,9 @@ public void wildcardNamespaceMatchesNoNamespace() { assertEquals(0, e.size()); assertNotEquals(a, e); } + + @Test public void selectorExceptionNotStringFormatException() { + Selector.SelectorParseException ex = new Selector.SelectorParseException("%&"); + assertEquals("%&", ex.getMessage()); + } } From 7299a7c6f0a25d68f1e2e0ca6ab80584b432edeb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 13:58:15 +1100 Subject: [PATCH 688/774] Moved xpath support out of beta --- src/main/java/org/jsoup/nodes/Element.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index fa541d5661..0a20456fba 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -508,9 +508,7 @@ public boolean is(Evaluator evaluator) { } /** - <b>Beta:</b> find Elements that match the supplied XPath expression. - <p>(This functionality is currently in beta and is subject to change. Feedback on the API is requested and - welcomed!)</p> + Find Elements that match the supplied XPath expression. <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an alternate XPathFactory implementation:</p> <ol> @@ -530,7 +528,7 @@ public Elements selectXpath(String xpath) { } /** - <b>Beta:</b> find Nodes that match the supplied XPath expression. + Find Nodes that match the supplied XPath expression. <p>For example, to select TextNodes under {@code p} elements: </p> <pre>List&lt;TextNode&gt; textNodes = doc.selectXpath("//body//p//text()", TextNode.class);</pre> <p>Note that in the jsoup DOM, Attribute objects are not Nodes. To directly select attribute values, do something From f8decb298cc9f6de8e181aa4ad72898c31aef341 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 15:25:54 +1100 Subject: [PATCH 689/774] Perf: cap the number of active formatting elements Also, use add() vs put() when applicable to save scanning for existing attributes in addAll() Fixes #1695 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Attributes.java | 8 ++++++-- .../java/org/jsoup/parser/HtmlTreeBuilder.java | 5 ++++- src/test/resources/fuzztests/1695.html.gz | Bin 0 -> 3937 bytes 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/fuzztests/1695.html.gz diff --git a/CHANGES b/CHANGES index f72c4a4e16..45172d4558 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,10 @@ jsoup changelog that could throw an IllegalFormatException. <https://github.com/jhy/jsoup/issues/1691> + * Bugfix [Fuzz]: speed improvement when parsing constructed HTML containing very deeply incorrectly stacked formatting + elements with many attributes. + <https://github.com/jhy/jsoup/issues/1695> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 97c4b958a8..492dfd9ac7 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -271,9 +271,13 @@ public void addAll(Attributes incoming) { return; checkCapacity(size + incoming.size); + boolean needsPut = size != 0; // if this set is empty, no need to check existing set, so can add() vs put() + // (and save bashing on the indexOfKey() for (Attribute attr : incoming) { - // todo - should this be case insensitive? - put(attr); + if (needsPut) + put(attr); + else + add(attr.getKey(), attr.getValue()); } } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 1c82827713..aaf415ff66 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -724,7 +724,10 @@ void pushWithBookmark(Element in, int bookmark){ void checkActiveFormattingElements(Element in){ int numSeen = 0; - for (int pos = formattingElements.size() -1; pos >= 0; pos--) { + final int size = formattingElements.size() -1; + int ceil = size - maxUsedFormattingElements; if (ceil <0) ceil = 0; + + for (int pos = size; pos >= ceil; pos--) { Element el = formattingElements.get(pos); if (el == null) // marker break; diff --git a/src/test/resources/fuzztests/1695.html.gz b/src/test/resources/fuzztests/1695.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..48f03de2ea97da11e694ad35005d871195a7a3c0 GIT binary patch literal 3937 zcmb2|=HT#tb2yQK+0e|=R4=0>H;3WvwS%6A6BybO*ULTP3o7h-Waq@U&F_I)-UF7o ztC&|j@b%(edSQS5ykt41pFiTAjLyH_{`K{ixpK>w|9(FGe)s$JKhNLezp?Q5joexL zaxZVs{k6^a{<fF5&1!Ei%fJ04clM|tJpv?orrc=Yk0!i<%XACg+S`{&=^q=-@%W3t zZ7%mOe*3dM+kXGO`I$QzE`Q%)d2s1yAvs!WjMkW=t%cF%(`eIiwAnmb8;;h715+D* zZ^>o5wMhSRdEm1A18z49BX@uPo3cF*7#;D?kAJbf{b&D%Mo=e1dP87Y%*in2Udtr@ SxC1NxGL+fH@gJVU#Q*>jF&xnV literal 0 HcmV?d00001 From 89bfc663d5d4feaad87213619f11a40f78140558 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 15:34:44 +1100 Subject: [PATCH 690/774] Testcase for #1696 I wasn't able to repro the timeout; parsing was very quick. Fixed in an earlier change? --- src/test/resources/fuzztests/1696.html.gz | Bin 0 -> 224 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/test/resources/fuzztests/1696.html.gz diff --git a/src/test/resources/fuzztests/1696.html.gz b/src/test/resources/fuzztests/1696.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..a40311ccf6aa3ce0ad5c30e8b8429c56e51468cd GIT binary patch literal 224 zcmb2|=HOWS>u@3iv!R)#nO;UoZVtoS>xNv13`E!x-Y+UCE8wY^Hcdn;gnQP}v%(d! z?t3SOUvSlJ3JB<!@_x45pC=#Fy}KryTD7+Ow4zAjW!-1B-yayhTetkPcE+V$bL%IB zeO>;-I#m4o`JIv_OIDt?$f>-!Tl;_V<+nxQ3=9b^x3-*(Z8SIJ`?H6U0TWC(TWB@& t$vaF5xX1yHC0gao3=E%MKeB5H<=B6B*OzN72|a1%VnM6U`#)r0001^URk{EG literal 0 HcmV?d00001 From ae61237a7d2b02b5e82dfef6c9729e00409d77d3 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 17:25:32 +1100 Subject: [PATCH 691/774] Break out of InTableBody correctly If resetInsertionMode doesn't change the current state, break out. Fixes #1697 --- CHANGES | 4 ++++ .../java/org/jsoup/parser/HtmlTreeBuilder.java | 9 ++++++++- .../org/jsoup/parser/HtmlTreeBuilderState.java | 3 +-- src/test/resources/fuzztests/1697.html.gz | Bin 0 -> 1325 bytes 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/fuzztests/1697.html.gz diff --git a/CHANGES b/CHANGES index 45172d4558..f2a6dae689 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,10 @@ jsoup changelog elements with many attributes. <https://github.com/jhy/jsoup/issues/1695> + * Bugfix [Fuzz]: during parsing, a StackOverflowException was possible given crafted HTML with hundreds of nested + table elements followed by invalid formatting elements. + <https://github.com/jhy/jsoup/issues/1697> + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) <https://github.com/jhy/jsoup/pull/1629> diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index aaf415ff66..ad048bb071 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -466,11 +466,17 @@ private void replaceInQueue(ArrayList<Element> queue, Element out, Element in) { queue.set(i, in); } - void resetInsertionMode() { + /** + * Reset the insertion mode, by searching up the stack for an appropriate insertion mode. The stack search depth + * is limited to {@link #maxQueueDepth}. + * @return true if the insertion mode was actually changed. + */ + boolean resetInsertionMode() { // https://html.spec.whatwg.org/multipage/parsing.html#the-insertion-mode boolean last = false; final int bottom = stack.size() - 1; final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0; + final HtmlTreeBuilderState origState = this.state; if (stack.size() == 0) { // nothing left of stack, just get to body transition(HtmlTreeBuilderState.InBody); @@ -539,6 +545,7 @@ void resetInsertionMode() { break; } } + return state != origState; } // todo: tidy up in specific scope methods diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index ae461e92ef..3a5236e68e 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -997,8 +997,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { return false; } else { tb.popStackToClose(name); - tb.resetInsertionMode(); - if (tb.state() == InTable) { + if (!tb.resetInsertionMode()) { // not per spec - but haven't transitioned out of table. so try something else tb.insert(startTag); return true; diff --git a/src/test/resources/fuzztests/1697.html.gz b/src/test/resources/fuzztests/1697.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..507fec4f783a0e5302be9d2c9441f661328f56f8 GIT binary patch literal 1325 zcmb2|=HOsuIFiV~Y-na_u9s1go5S$-x_6#(D1*br6vj7=hRmB9C%OAXF>!W^T69Hc zp8R=2g!$TmWfB`W%bag^PA)rfK9G6elOvOYwSHgS`F?MZm*ladN52-?>9rNs?Yi^7 zw&JjWicqJE(nJqxQYZGF-0uFj{Pah@r(wD?eblCpQjj31+xmxZdCiBS)zf^`CZ8Uq zs22n^6`O8%0|S-cDevl}lPN`!)Tch#ecH}$_S`vE-DsxIzyBxKMgeKP(*&rW2cC34 zee<Hi!g{vuwdT4zJXSON(#o&@_?2R{&;Hh&z5nX$`nr$iSQQr8`CXUjtFzx{DYNeV z&-HDEro2BZwz9Q-tofB<T3Z*s{Z7E^Zryi!CcPF`cDA<hXU@m{dbjxD?)y7dM%Dc~ zQ@^I5)F>`)?z(Ht-!)?EE!LhamfXF1NAaHEA1`*TO?&=W&hfnd@gr|aS9Yggnr~Bg zs6O<?<=fvcJ*rvwx@KePKlSaO->&|$Vy9(Qj=#LbYwMr!^A6u_oj>nP?uUE(|C!yM zo?d7_ar?Qf>Xm0_et!C<|9;&6@U(xwWan=CoOm?t#IMhH1J%24&a-~L@BtDm@ZfK? z|NismvyGF(r_a}0UoUBIZ~AA$k4!_p#~%uR|9W@$@5i^V4}XyRpIgWNY0J*rwsCjg ze@m*meNW83f4`o%`SzpVqVreWz2|nGzpg+#&+p#7{@GXNS{KdUH!HtwZMx+jRwDtu JB&!2V3;@GskBtBT literal 0 HcmV?d00001 From d0df41957bb9f1530c9cb6678e2b59fa8926ae81 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 18:48:07 +1100 Subject: [PATCH 692/774] Perf tweaks for #1695 --- src/main/java/org/jsoup/nodes/Attributes.java | 4 ++-- .../java/org/jsoup/parser/HtmlTreeBuilder.java | 8 ++++---- .../java/org/jsoup/nodes/AttributesTest.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 492dfd9ac7..2e989fd385 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -415,8 +415,8 @@ public Attributes clone() { throw new RuntimeException(e); } clone.size = size; - keys = Arrays.copyOf(keys, size); - vals = Arrays.copyOf(vals, size); + clone.keys = Arrays.copyOf(keys, size); + clone.vals = Arrays.copyOf(vals, size); return clone; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index ad048bb071..7ddda8e7d9 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -758,6 +758,8 @@ private boolean isSameFormattingElement(Element a, Element b) { } void reconstructFormattingElements() { + if (stack.size() > maxQueueDepth) + return; Element last = lastFormattingElement(); if (last == null || onStack(last)) return; @@ -783,10 +785,8 @@ void reconstructFormattingElements() { // 8. create new element from element, 9 insert into current node, onto stack skip = false; // can only skip increment from 4. - Element newEl = insertStartTag(entry.normalName()); // todo: avoid fostering here? - // newEl.namespace(entry.namespace()); // todo: namespaces - if (entry.attributesSize() > 0) - newEl.attributes().addAll(entry.attributes()); + Element newEl = new Element(tagFor(entry.normalName(), settings), null, entry.attributes().clone()); + insert(newEl); // 10. replace entry with new entry formattingElements.set(pos, newEl); diff --git a/src/test/java/org/jsoup/nodes/AttributesTest.java b/src/test/java/org/jsoup/nodes/AttributesTest.java index 16a60b835d..58024c6aa6 100644 --- a/src/test/java/org/jsoup/nodes/AttributesTest.java +++ b/src/test/java/org/jsoup/nodes/AttributesTest.java @@ -301,4 +301,21 @@ public void testBoolean() { assertEquals(four, four.clone()); assertNotEquals(one, four); } + + @Test void cloneAttributes() { + Attributes one = new Attributes() + .add("Key1", "Val1") + .add("Key2", "Val2") + .add("Key3", null); + Attributes two = one.clone(); + assertEquals(3, two.size()); + assertEquals("Val2", two.get("Key2")); + assertEquals(one, two); + + two.add("Key4", "Val4"); + assertEquals(4, two.size()); + assertEquals(3, one.size()); + assertNotEquals(one, two); + + } } From 5d94d2af069f8b5a51f5902b8079c18efede2350 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Thu, 23 Dec 2021 18:55:19 +1100 Subject: [PATCH 693/774] Extend the timeout a little Not sure the best path here - the CPU available is going to vary, and the Fuzzer is great at finding perf issues right on the boundary. --- src/test/java/org/jsoup/integration/FuzzFixesIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jsoup/integration/FuzzFixesIT.java b/src/test/java/org/jsoup/integration/FuzzFixesIT.java index a060de561b..4704ab612c 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesIT.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesIT.java @@ -20,7 +20,7 @@ */ public class FuzzFixesIT { static int numIters = 50; - static int timeout = 20; // external fuzzer is set to 60 for 100 runs + static int timeout = 30; // external fuzzer is set to 60 for 100 runs static File testDir = ParseTest.getFile("/fuzztests/"); private static Stream<File> testFiles() { From 95a767e43abf11c28f94252a01699d7a961f93fb Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 27 Dec 2021 15:05:12 +1100 Subject: [PATCH 694/774] Perf tweak for formatter elements Improves #1695 --- .../jsoup/parser/HtmlTreeBuilderState.java | 38 ++++++++++++++----- .../parser/HtmlTreeBuilderStateTest.java | 2 +- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 3a5236e68e..af1fefe453 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -630,14 +630,38 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { } // todo - is this right? drops rp, rt if ruby not in scope? break; + // InBodyStartEmptyFormatters: + case "area": + case "br": + case "embed": + case "img": + case "keygen": + case "wbr": + tb.reconstructFormattingElements(); + tb.insertEmpty(startTag); + tb.framesetOk(false); + break; + // Formatters: + case "b": + case "big": + case "code": + case "em": + case "font": + case "i": + case "s": + case "small": + case "strike": + case "strong": + case "tt": + case "u": + tb.reconstructFormattingElements(); + el = tb.insert(startTag); + tb.pushActiveFormattingElements(el); + break; default: // todo - bring scan groups in if desired if (!Tag.isKnownTag(name)) { // no special rules for custom tags tb.insert(startTag); - } else if (inSorted(name, Constants.InBodyStartEmptyFormatters)) { - tb.reconstructFormattingElements(); - tb.insertEmpty(startTag); - tb.framesetOk(false); } else if (inSorted(name, Constants.InBodyStartPClosers)) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); @@ -645,10 +669,6 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { tb.insert(startTag); } else if (inSorted(name, Constants.InBodyStartToHead)) { return tb.process(t, InHead); - } else if (inSorted(name, Constants.Formatters)) { - tb.reconstructFormattingElements(); - el = tb.insert(startTag); - tb.pushActiveFormattingElements(el); } else if (inSorted(name, Constants.InBodyStartApplets)) { tb.reconstructFormattingElements(); tb.insert(startTag); @@ -1756,9 +1776,7 @@ static final class Constants { static final String[] Headings = new String[]{"h1", "h2", "h3", "h4", "h5", "h6"}; static final String[] InBodyStartLiBreakers = new String[]{"address", "div", "p"}; static final String[] DdDt = new String[]{"dd", "dt"}; - static final String[] Formatters = new String[]{"b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u"}; static final String[] InBodyStartApplets = new String[]{"applet", "marquee", "object"}; - static final String[] InBodyStartEmptyFormatters = new String[]{"area", "br", "embed", "img", "keygen", "wbr"}; static final String[] InBodyStartMedia = new String[]{"param", "source", "track"}; static final String[] InBodyStartInputAttribs = new String[]{"action", "name", "prompt"}; static final String[] InBodyStartDrop = new String[]{"caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"}; diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 5257975a11..3011609f1f 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -45,7 +45,7 @@ static void ensureSorted(List<Object[]> constants) { public void ensureArraysAreSorted() { List<Object[]> constants = findConstantArrays(Constants.class); ensureSorted(constants); - assertEquals(40, constants.size()); + assertEquals(38, constants.size()); } @Test public void ensureTagSearchesAreKnownTags() { From 2b6041f3ed7a0bbef64d33f53ba1b44043de2d74 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 27 Dec 2021 16:38:25 +1100 Subject: [PATCH 695/774] When cloning, shallow clone the OwnerDocument This keeps the element's output settings preserved Fixes #763 --- CHANGES | 4 +++ src/main/java/org/jsoup/nodes/Document.java | 9 ++++++ src/main/java/org/jsoup/nodes/Element.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 9 ++++++ .../java/org/jsoup/nodes/ElementTest.java | 29 ++++++++++++++++++- src/test/java/org/jsoup/nodes/NodeTest.java | 22 ++++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index f2a6dae689..92c19cfcd5 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,10 @@ jsoup changelog 30 to limit the indent level for very deeply nested elements, and may be disabled by setting to -1. <https://github.com/jhy/jsoup/pull/1655> + * Improvement: when cloning a Node or an Element, the clone gets a cloned OwnerDocument containing only that clone, so + as to preserve applicable settings, such as the Pretty Print settings. + <https://github.com/jhy/jsoup/issues/763> + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. <https://github.com/jhy/jsoup/issues/1656> diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 6e5d8e3202..b9ace3352c 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -338,6 +338,15 @@ public Document clone() { clone.outputSettings = this.outputSettings.clone(); return clone; } + + @Override + public Document shallowClone() { + Document clone = new Document(baseUri()); + if (attributes != null) + clone.attributes = attributes.clone(); + clone.outputSettings = this.outputSettings.clone(); + return clone; + } /** * Ensures a meta charset (html) or xml declaration (xml) with the current diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 0a20456fba..795fc0100a 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -47,7 +47,7 @@ public class Element extends Node { private Tag tag; private @Nullable WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children List<Node> childNodes; - private @Nullable Attributes attributes; // field is nullable but all methods for attributes are non null + protected @Nullable Attributes attributes; // field is nullable but all methods for attributes are non null /** * Create a new, standalone element. diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 27886f46b0..e0520ebee5 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -781,6 +781,15 @@ protected Node doClone(@Nullable Node parent) { clone.parentNode = parent; // can be null, to create an orphan split clone.siblingIndex = parent == null ? 0 : siblingIndex; + // if not keeping the parent, shallowClone the ownerDocument to preserve its settings + if (parent == null && !(this instanceof Document)) { + Document doc = ownerDocument(); + if (doc != null) { + Document docClone = doc.shallowClone(); + clone.parentNode = docClone; + docClone.ensureChildNodes().add(clone); + } + } return clone; } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 563442bd04..712541dbcc 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -866,7 +866,10 @@ public void testClone() { Element p = doc.select("p").get(1); Element clone = p.clone(); - assertNull(clone.parent()); // should be orphaned + assertNotNull(clone.parentNode); // should be a cloned document just containing this clone + assertEquals(1, clone.parentNode.childNodeSize()); + assertSame(clone.ownerDocument(), clone.parentNode); + assertEquals(0, clone.siblingIndex); assertEquals(1, p.siblingIndex); assertNotNull(p.parent()); @@ -2099,4 +2102,28 @@ public void childNodesAccessorDoesNotVivify() { p.removeAttr("foo"); assertEquals(0, p.attributesSize()); } + + @Test void clonedElementsHaveOwnerDocsAndIndependentSettings() { + // https://github.com/jhy/jsoup/issues/763 + Document doc = Jsoup.parse("<div>Text</div><div>Two</div>"); + doc.outputSettings().prettyPrint(false); + Element div = doc.selectFirst("div"); + assertNotNull(div); + Node text = div.childNode(0); + assertNotNull(text); + + Element divClone = div.clone(); + Document docClone = divClone.ownerDocument(); + assertNotNull(docClone); + assertFalse(docClone.outputSettings().prettyPrint()); + assertNotSame(doc, docClone); + assertSame(docClone, divClone.childNode(0).ownerDocument()); + // the cloned text has same owner doc as the cloned div + + doc.outputSettings().prettyPrint(true); + assertTrue(doc.outputSettings().prettyPrint()); + assertFalse(docClone.outputSettings().prettyPrint()); + assertEquals(1, docClone.children().size()); // check did not get the second div as the owner's children + assertEquals(divClone, docClone.child(0)); // note not the head or the body -- not normalized + } } diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index 9dc1b2272d..fc8aaa7fce 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -332,4 +332,26 @@ private Attributes singletonAttributes() { attributes.put("value", "bar"); return attributes; } + + @Test void clonedNodesHaveOwnerDocsAndIndependentSettings() { + // https://github.com/jhy/jsoup/issues/763 + Document doc = Jsoup.parse("<div>Text</div><div>Two</div>"); + doc.outputSettings().prettyPrint(false); + Element div = doc.selectFirst("div"); + assertNotNull(div); + TextNode text = (TextNode) div.childNode(0); + assertNotNull(text); + + TextNode textClone = text.clone(); + Document docClone = textClone.ownerDocument(); + assertNotNull(docClone); + assertFalse(docClone.outputSettings().prettyPrint()); + assertNotSame(doc, docClone); + + doc.outputSettings().prettyPrint(true); + assertTrue(doc.outputSettings().prettyPrint()); + assertFalse(docClone.outputSettings().prettyPrint()); + assertEquals(1, docClone.childNodes().size()); // check did not get the second div as the owner's children + assertEquals(textClone, docClone.childNode(0)); // note not the head or the body -- not normalized + } } From 18c3d996cca8ed592179cf0fe311eb75b41d2b8c Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Mon, 27 Dec 2021 17:17:10 +1100 Subject: [PATCH 696/774] Lower Attributes visibility (Still higher than private, as in the current release; visible for Document clone) --- src/main/java/org/jsoup/nodes/Element.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 795fc0100a..8d30a10dbb 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -47,7 +47,7 @@ public class Element extends Node { private Tag tag; private @Nullable WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children List<Node> childNodes; - protected @Nullable Attributes attributes; // field is nullable but all methods for attributes are non null + @Nullable Attributes attributes; // field is nullable but all methods for attributes are non-null /** * Create a new, standalone element. From aeaeb920c5688604f6e3ccc38b5d3a3f6901ffdd Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 10:58:20 +1100 Subject: [PATCH 697/774] Javadoc update --- src/test/java/org/jsoup/integration/FuzzFixesIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jsoup/integration/FuzzFixesIT.java b/src/test/java/org/jsoup/integration/FuzzFixesIT.java index 4704ab612c..a03758dd67 100644 --- a/src/test/java/org/jsoup/integration/FuzzFixesIT.java +++ b/src/test/java/org/jsoup/integration/FuzzFixesIT.java @@ -15,8 +15,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /** - Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup As some of these - are timeout tests - run each file 100 times and ensure under time. + Tests fixes for issues raised by the <a href="https://oss-fuzz.com/testcases?project=jsoup">OSS Fuzz project</a>. As + some of these are timeout tests - run each file 100 times and ensure under time. */ public class FuzzFixesIT { static int numIters = 50; From 3a6e7fa748edc8893e7b110cd530662f53725cee Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 13:01:30 +1100 Subject: [PATCH 698/774] Added Jsoup.parse(File) method Fixes #1693 --- CHANGES | 3 +++ src/main/java/org/jsoup/Jsoup.java | 18 +++++++++++++++++- .../java/org/jsoup/integration/ParseTest.java | 7 +++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 92c19cfcd5..073e7e5762 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,9 @@ jsoup changelog as to preserve applicable settings, such as the Pretty Print settings. <https://github.com/jhy/jsoup/issues/763> + * Improvement: added a convenience method Jsoup.parse(File). + <https://github.com/jhy/jsoup/issues/1693> + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. <https://github.com/jhy/jsoup/issues/1656> diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index f6cb7c73b0..677b347841 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -142,12 +142,28 @@ public static Document parse(File file, @Nullable String charsetName, String bas @return sane HTML @throws IOException if the file could not be found, or read, or if the charsetName is invalid. - @see #parse(File, String, String) + @see #parse(File, String, String) parse(file, charset, baseUri) */ public static Document parse(File file, @Nullable String charsetName) throws IOException { return DataUtil.load(file, charsetName, file.getAbsolutePath()); } + /** + Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs. + The charset used to read the file will be determined by the byte-order-mark (BOM), or a {@code <meta charset>} tag, + or if neither is present, will be {@code UTF-8}. + + <p>This is the equivalent of calling {@link #parse(File, String) parse(file, null)}</p> + + @param file the file to load HTML from. Supports gzipped files (ending in .z or .gz). + @return sane HTML + @throws IOException if the file could not be found or read. + @see #parse(File, String, String) parse(file, charset, baseUri) + */ + public static Document parse(File file) throws IOException { + return DataUtil.load(file, null, file.getAbsolutePath()); + } + /** Parse the contents of a file as HTML. diff --git a/src/test/java/org/jsoup/integration/ParseTest.java b/src/test/java/org/jsoup/integration/ParseTest.java index f3c1dfcb4c..0bcbb8e87a 100644 --- a/src/test/java/org/jsoup/integration/ParseTest.java +++ b/src/test/java/org/jsoup/integration/ParseTest.java @@ -229,6 +229,13 @@ public void testXwikiExpanded() throws IOException { assertEquals(wantHtml, doc.select("[data-id=userdirectory]").outerHtml()); } + @Test public void testFileParseNoCharsetMethod() throws IOException { + File in = getFile("/htmltests/xwiki-1324.html.gz"); + Document doc = Jsoup.parse(in); + assertEquals("XWiki Jetty HSQLDB 12.1-SNAPSHOT", doc.select("#xwikiplatformversion").text()); + } + + public static File getFile(String resourceName) { try { URL resource = ParseTest.class.getResource(resourceName); From 99f925857c0c5bdecf02a729265291f3aec8d616 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 13:03:02 +1100 Subject: [PATCH 699/774] Note since release --- src/main/java/org/jsoup/Jsoup.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 677b347841..732fde7da8 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -159,6 +159,7 @@ public static Document parse(File file, @Nullable String charsetName) throws IOE @return sane HTML @throws IOException if the file could not be found or read. @see #parse(File, String, String) parse(file, charset, baseUri) + @since 1.15.1 */ public static Document parse(File file) throws IOException { return DataUtil.load(file, null, file.getAbsolutePath()); From 027c70c94368c2793a42c0532d29f4e05a0307f8 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 16:06:35 +1100 Subject: [PATCH 700/774] Added the :containsWholeOwnText selector And the corresponding Element#wholeOwnText() method. For #1636 --- CHANGES | 5 +++ src/main/java/org/jsoup/nodes/Element.java | 44 ++++++++++++++++--- src/main/java/org/jsoup/select/Evaluator.java | 23 ++++++++++ .../java/org/jsoup/select/QueryParser.java | 9 ++++ src/main/java/org/jsoup/select/Selector.java | 3 +- .../java/org/jsoup/parser/HtmlParserTest.java | 9 ++++ .../java/org/jsoup/select/SelectorTest.java | 20 +++++++++ 7 files changed, 106 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 073e7e5762..e52bf92734 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,11 @@ jsoup changelog useful when elements can only be distinguished by e.g. specific case, or leading whitespace, etc. <https://github.com/jhy/jsoup/issues/1636> + * Improvement: added Element#wholeOwnText() to retrieve the original (non-normalized) ownText of an Element. Also + added the :containsWholeOwnText(text) selector, to match against that. BR elements are now treated as newlines + in the wholeText methods. + <https://github.com/jhy/jsoup/issues/1636> + * Improvement: when evaluating an XPath query against a context element, the complete document is now visible to the query, vs only the context element's sub-tree. This enables support for queries outside (parent or sibling) the element, e.g. ancestor-or-self::*. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 8d30a10dbb..f96582f490 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1285,19 +1285,44 @@ public String wholeText() { final StringBuilder accum = StringUtil.borrowBuilder(); NodeTraversor.traverse(new NodeVisitor() { public void head(Node node, int depth) { - if (node instanceof TextNode) { - TextNode textNode = (TextNode) node; - accum.append(textNode.getWholeText()); - } + appendWholeText(node, accum); } - public void tail(Node node, int depth) { - } + public void tail(Node node, int depth) {} }, this); return StringUtil.releaseBuilder(accum); } + private static void appendWholeText(Node node, StringBuilder accum) { + if (node instanceof TextNode) { + accum.append(((TextNode) node).getWholeText()); + } else if (node instanceof Element) { + appendNewlineIfBr((Element) node, accum); + } + } + + /** + Get the (unencoded) text of this element, <b>not including</b> any child elements, including any newlines and spaces + present in the original. + + @return unencoded, un-normalized text that is a direct child of this Element + @see #text() + @see #wholeText() + @see #ownText() + @since 1.15.1 + */ + public String wholeOwnText() { + final StringBuilder accum = StringUtil.borrowBuilder(); + final int size = childNodeSize(); + for (int i = 0; i < size; i++) { + Node node = childNodes.get(i); + appendWholeText(node, accum); + } + + return StringUtil.releaseBuilder(accum); + } + /** * Gets the (normalized) text owned by this element only; does not get the combined text of all children. * <p> @@ -1336,11 +1361,18 @@ private static void appendNormalisedText(StringBuilder accum, TextNode textNode) StringUtil.appendNormalisedWhitespace(accum, text, TextNode.lastCharIsWhitespace(accum)); } + /** For normalized text, treat a br element as a space, if there is not already a space. */ private static void appendWhitespaceIfBr(Element element, StringBuilder accum) { if (element.tag.normalName().equals("br") && !TextNode.lastCharIsWhitespace(accum)) accum.append(" "); } + /** For WholeText, treat a br element as a newline. */ + private static void appendNewlineIfBr(Element element, StringBuilder accum) { + if (element.tag.normalName().equals("br")) + accum.append("\n"); + } + static boolean preserveWhitespace(@Nullable Node node) { // looks only at this element and five levels up, to prevent recursion & needless stack searches if (node instanceof Element) { diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index 8c319f33bf..a73b7c6893 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -704,6 +704,29 @@ public String toString() { } } + /** + * Evaluator for matching Element (but <b>not</b> its descendants) wholeText. Neither the input nor the element text is + * normalized. <code>:containsWholeOwnText()</code> + * @since 1.15.1. + */ + public static final class ContainsWholeOwnText extends Evaluator { + private final String searchText; + + public ContainsWholeOwnText(String searchText) { + this.searchText = searchText; + } + + @Override + public boolean matches(Element root, Element element) { + return element.wholeOwnText().contains(searchText); + } + + @Override + public String toString() { + return String.format(":containsWholeOwnText(%s)", searchText); + } + } + /** * Evaluator for matching Element (and its descendants) data */ diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 4611624151..5139493c29 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -182,6 +182,8 @@ else if (tq.matches(":containsOwn(")) contains(true); else if (tq.matches(":containsWholeText(")) containsWholeText(); + else if (tq.matches(":containsWholeOwnText(")) + containsWholeOwnText(); else if (tq.matches(":containsData(")) containsData(); else if (tq.matches(":matches(")) @@ -376,6 +378,13 @@ private void containsWholeText() { evals.add(new Evaluator.ContainsWholeText(searchText)); } + private void containsWholeOwnText() { + tq.consume(":containsWholeOwnText"); + String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); + Validate.notEmpty(searchText, ":containsWholeOwnText(text) query must not be empty"); + evals.add(new Evaluator.ContainsWholeOwnText(searchText)); + } + // pseudo selector :containsData(data) private void containsData() { tq.consume(":containsData"); diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 8e86bf8370..4361cc8b6f 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -53,7 +53,8 @@ * <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contains the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants. The text is whitespace normalized. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:contains(jsoup)</code> finds p elements containing the text "jsoup".<p>{@code p:contains(hello \(there\) finds p elements containing the text "Hello (There)"}</p></td></tr> * <tr><td><code>:containsOwn(<em>text</em>)</code></td><td>elements that directly contain the specified text. The search is case insensitive. The text must appear in the found element, not any of its descendants.</td><td><code>p:containsOwn(jsoup)</code> finds p elements with own text "jsoup".</td></tr> * <tr><td><code>:containsData(<em>data</em>)</code></td><td>elements that contains the specified <em>data</em>. The contents of {@code script} and {@code style} elements, and {@code comment} nodes (etc) are considered data nodes, not text nodes. The search is case insensitive. The data may appear in the found element, or any of its descendants.</td><td><code>script:contains(jsoup)</code> finds script elements containing the data "jsoup".</td></tr> - * <tr><td><code>:containsWholeText(<em>text</em>)</code></td><td>elements that contains the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, or any of its descendants. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:containsWholeText(jsoup\nThe Java HTML Parser)</code> finds p elements containing the text <code>"jsoup\nThe Java HTML Parser"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would.</p></td></tr> + * <tr><td><code>:containsWholeText(<em>text</em>)</code></td><td>elements that contains the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, or any of its descendants. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:containsWholeText(jsoup\nThe Java HTML Parser)</code> finds p elements containing the text <code>"jsoup\nThe Java HTML Parser"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would. Note that {@code br} elements are presented as a newline.</p></td></tr> + * <tr><td><code>:containsWholeOwnText(<em>text</em>)</code></td><td>elements that <b>directly</b> contain the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, but not in its descendants. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:containsWholeOwnText(jsoup\nThe Java HTML Parser)</code> finds p elements directly containing the text <code>"jsoup\nThe Java HTML Parser"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would. Note that {@code br} elements are presented as a newline.</p></td></tr> * <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements containing <b>whitespace normalized</b> text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> * <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesOwn(\\d+)</code> finds table cells directly containing digits. <code>div:matchesOwn((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> * <tr><td></td><td>The above may be combined in any order and with other selectors</td><td><code>.light:contains(name):eq(0)</code></td></tr> diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 08accbbd35..5214f1cbaf 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1429,6 +1429,15 @@ public void testUNewlines() { assertEquals(html, doc.body().html()); // disabling pretty-printing - round-trips the tab throughout, as no normalization occurs } + @Test void wholeTextTreatsBRasNewline() { + String html = "<div>\nOne<br>Two <p>Three<br>Four</div>"; + Document doc = Jsoup.parse(html); + Element div = doc.selectFirst("div"); + assertNotNull(div); + assertEquals("\nOne\nTwo Three\nFour", div.wholeText()); + assertEquals("\nOne\nTwo ", div.wholeOwnText()); + } + @Test public void canDetectAutomaticallyAddedElements() { String bare = "<script>One</script>"; String full = "<html><head><title>Check</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p></body></html>"; diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 8adff3a45e..e1cf6ddaa3 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -637,6 +637,26 @@ public void testPseudoContains(Locale locale) { assertEquals(". ", blanks.first().wholeText()); } + @Test void containsWholeOwnText() { + Document doc = Jsoup.parse("<div><p> jsoup\n The <i>HTML</i> Parser</p><p>jsoup The HTML Parser<br></div>"); + Elements ps = doc.select("p"); + + Elements es1 = doc.select("p:containsWholeOwnText( jsoup\n The Parser)"); + Elements es2 = doc.select("p:containsWholeOwnText(jsoup The HTML Parser\n)"); + assertEquals(1, es1.size()); + assertEquals(1, es2.size()); + assertEquals(ps.get(0), es1.first()); + assertEquals(ps.get(1), es2.first()); + + assertEquals(0, doc.select("div:containsWholeOwnText(jsoup the html parser)").size()); + assertEquals(0, doc.select("div:containsWholeOwnText(jsoup\n the parser)").size()); + + doc = Jsoup.parse("<div><p></p><p> </p><p>. </p>"); + Elements blanks = doc.select("p:containsWholeOwnText( )"); + assertEquals(1, blanks.size()); + assertEquals(". ", blanks.first().wholeText()); + } + @MultiLocaleTest public void containsOwn(Locale locale) { Locale.setDefault(locale); From ab1d80bbe65788925f798d4313361285b1e7216c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 16:29:53 +1100 Subject: [PATCH 701/774] Simplified ownText evaluator --- .../java/org/jsoup/select/QueryParser.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 5139493c29..a14da483f6 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -181,9 +181,9 @@ else if (tq.matches(":contains(")) else if (tq.matches(":containsOwn(")) contains(true); else if (tq.matches(":containsWholeText(")) - containsWholeText(); + containsWholeText(false); else if (tq.matches(":containsWholeOwnText(")) - containsWholeOwnText(); + containsWholeText(true); else if (tq.matches(":containsData(")) containsData(); else if (tq.matches(":matches(")) @@ -362,27 +362,23 @@ private void has() { // pseudo selector :contains(text), containsOwn(text) private void contains(boolean own) { - tq.consume(own ? ":containsOwn" : ":contains"); + String query = own ? ":containsOwn" : ":contains"; + tq.consume(query); String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); - Validate.notEmpty(searchText, ":contains(text) query must not be empty"); - if (own) - evals.add(new Evaluator.ContainsOwnText(searchText)); - else - evals.add(new Evaluator.ContainsText(searchText)); - } - - private void containsWholeText() { - tq.consume(":containsWholeText"); - String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); - Validate.notEmpty(searchText, ":containsWholeText(text) query must not be empty"); - evals.add(new Evaluator.ContainsWholeText(searchText)); + Validate.notEmpty(searchText, query + "(text) query must not be empty"); + evals.add(own + ? new Evaluator.ContainsOwnText(searchText) + : new Evaluator.ContainsText(searchText)); } - private void containsWholeOwnText() { - tq.consume(":containsWholeOwnText"); + private void containsWholeText(boolean own) { + String query = own ? ":containsWholeOwnText" : ":containsWholeText"; + tq.consume(query); String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); - Validate.notEmpty(searchText, ":containsWholeOwnText(text) query must not be empty"); - evals.add(new Evaluator.ContainsWholeOwnText(searchText)); + Validate.notEmpty(searchText, query + "(text) query must not be empty"); + evals.add(own + ? new Evaluator.ContainsWholeOwnText(searchText) + : new Evaluator.ContainsWholeText(searchText)); } // pseudo selector :containsData(data) From 4535a57711d160318d8716618d8b9c5d6fd02394 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 17:15:47 +1100 Subject: [PATCH 702/774] Added selectors for matchesWholeText and matchesWholeOwnText Fixes #1636 --- CHANGES | 4 ++ src/main/java/org/jsoup/select/Evaluator.java | 44 +++++++++++++++++++ .../java/org/jsoup/select/QueryParser.java | 28 +++++++++--- src/main/java/org/jsoup/select/Selector.java | 3 +- .../java/org/jsoup/select/SelectorTest.java | 37 ++++++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index e52bf92734..39ec53602b 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,10 @@ jsoup changelog in the wholeText methods. <https://github.com/jhy/jsoup/issues/1636> + * Improvement: added the :matchesWholeText(regex) and :matchesWholeOwnText(regex) selectors, to match against whole + (non-normalized, case sensitive> element text and own text, respectively. + <https://github.com/jhy/jsoup/issues/1636> + * Improvement: when evaluating an XPath query against a context element, the complete document is now visible to the query, vs only the context element's sub-tree. This enables support for queries outside (parent or sibling) the element, e.g. ancestor-or-self::*. diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index a73b7c6893..d8d71777b6 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -813,6 +813,50 @@ public String toString() { } } + /** + * Evaluator for matching Element (and its descendants) whole text with regex + */ + public static final class MatchesWholeText extends Evaluator { + private final Pattern pattern; + + public MatchesWholeText(Pattern pattern) { + this.pattern = pattern; + } + + @Override + public boolean matches(Element root, Element element) { + Matcher m = pattern.matcher(element.wholeText()); + return m.find(); + } + + @Override + public String toString() { + return String.format(":matchesWholeText(%s)", pattern); + } + } + + /** + * Evaluator for matching Element's own whole text with regex + */ + public static final class MatchesWholeOwnText extends Evaluator { + private final Pattern pattern; + + public MatchesWholeOwnText(Pattern pattern) { + this.pattern = pattern; + } + + @Override + public boolean matches(Element root, Element element) { + Matcher m = pattern.matcher(element.wholeOwnText()); + return m.find(); + } + + @Override + public String toString() { + return String.format(":matchesWholeOwnText(%s)", pattern); + } + } + public static final class MatchText extends Evaluator { @Override diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index a14da483f6..3b2b735696 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -190,6 +190,10 @@ else if (tq.matches(":matches(")) matches(false); else if (tq.matches(":matchesOwn(")) matches(true); + else if (tq.matches(":matchesWholeText(")) + matchesWholeText(false); + else if (tq.matches(":matchesWholeOwnText(")) + matchesWholeText(true); else if (tq.matches(":not(")) not(); else if (tq.matchChomp(":nth-child(")) @@ -391,14 +395,26 @@ private void containsData() { // :matches(regex), matchesOwn(regex) private void matches(boolean own) { - tq.consume(own ? ":matchesOwn" : ":matches"); + String query = own ? ":matchesOwn" : ":matches"; + tq.consume(query); String regex = tq.chompBalanced('(', ')'); // don't unescape, as regex bits will be escaped - Validate.notEmpty(regex, ":matches(regex) query must not be empty"); + Validate.notEmpty(regex, query + "(regex) query must not be empty"); - if (own) - evals.add(new Evaluator.MatchesOwn(Pattern.compile(regex))); - else - evals.add(new Evaluator.Matches(Pattern.compile(regex))); + evals.add(own + ? new Evaluator.MatchesOwn(Pattern.compile(regex)) + : new Evaluator.Matches(Pattern.compile(regex))); + } + + // :matches(regex), matchesOwn(regex) + private void matchesWholeText(boolean own) { + String query = own ? ":matchesWholeOwnText" : ":matchesWholeText"; + tq.consume(query); + String regex = tq.chompBalanced('(', ')'); // don't unescape, as regex bits will be escaped + Validate.notEmpty(regex, query + "(regex) query must not be empty"); + + evals.add(own + ? new Evaluator.MatchesWholeOwnText(Pattern.compile(regex)) + : new Evaluator.MatchesWholeText(Pattern.compile(regex))); } // :not(selector) diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 4361cc8b6f..a204c47d7c 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -56,7 +56,8 @@ * <tr><td><code>:containsWholeText(<em>text</em>)</code></td><td>elements that contains the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, or any of its descendants. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:containsWholeText(jsoup\nThe Java HTML Parser)</code> finds p elements containing the text <code>"jsoup\nThe Java HTML Parser"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would. Note that {@code br} elements are presented as a newline.</p></td></tr> * <tr><td><code>:containsWholeOwnText(<em>text</em>)</code></td><td>elements that <b>directly</b> contain the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, but not in its descendants. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:containsWholeOwnText(jsoup\nThe Java HTML Parser)</code> finds p elements directly containing the text <code>"jsoup\nThe Java HTML Parser"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would. Note that {@code br} elements are presented as a newline.</p></td></tr> * <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements containing <b>whitespace normalized</b> text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> - * <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>elements whose own text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesOwn(\\d+)</code> finds table cells directly containing digits. <code>div:matchesOwn((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> + * <tr><td><code>:matchesWholeText(<em>regex</em>)</code></td><td>elements containing <b>non-normalized</b> whole text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matchesWholeText(\\s{2,})</code> finds table cells a run of at least two space characters.</td></tr> + * <tr><td><code>:matchesWholeOwnText(<em>regex</em>)</code></td><td>elements whose own <b>non-normalized</b> whole text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesWholeOwnText(\n\\d+)</code> finds table cells directly containing digits following a neewline.</td></tr> * <tr><td></td><td>The above may be combined in any order and with other selectors</td><td><code>.light:contains(name):eq(0)</code></td></tr> * <tr><td><code>:matchText</code></td><td>treats text nodes as elements, and so allows you to match against and select text nodes.<p><b>Note</b> that using this selector will modify the DOM, so you may want to {@code clone} your document before using.</td><td>{@code p:matchText:firstChild} with input {@code <p>One<br />Two</p>} will return one {@link org.jsoup.nodes.PseudoTextElement} with text "{@code One}".</td></tr> * <tr><td colspan="3"><h3>Structural pseudo selectors</h3></td></tr> diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index e1cf6ddaa3..b3780ac1f6 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -710,6 +710,43 @@ public void containsOwn(Locale locale) { assertEquals(0, doc.select("p:matchesOwn(there)").size()); } + @Test public void matchesWholeText() { + Document doc = Jsoup.parse("<p id=1>Hello <b>there</b>\n now</p><p id=2> </p><p id=3></p>"); + + Elements p1 = doc.select("p:matchesWholeText((?i)hello there\n now)"); + assertEquals(1, p1.size()); + assertEquals("1", p1.first().id()); + + assertEquals(1, doc.select("p:matchesWholeText(there\n now)").size()); + assertEquals(0, doc.select("p:matchesWholeText(There\n now)").size()); + + Elements p2 = doc.select("p:matchesWholeText(^\\s+$)"); + assertEquals(1, p2.size()); + assertEquals("2", p2.first().id()); + + Elements p3 = doc.select("p:matchesWholeText(^$)"); + assertEquals(1, p3.size()); + assertEquals("3", p3.first().id()); + } + + @Test public void matchesWholeOwnText() { + Document doc = Jsoup.parse("<p id=1>Hello <b>there</b>\n now</p><p id=2> </p><p id=3><i>Text</i></p>"); + + Elements p1 = doc.select("p:matchesWholeOwnText((?i)hello \n now)"); + assertEquals(1, p1.size()); + assertEquals("1", p1.first().id()); + + assertEquals(0, doc.select("p:matchesWholeOwnText(there\n now)").size()); + + Elements p2 = doc.select("p:matchesWholeOwnText(^\\s+$)"); + assertEquals(1, p2.size()); + assertEquals("2", p2.first().id()); + + Elements p3 = doc.select("p:matchesWholeOwnText(^$)"); + assertEquals(1, p3.size()); + assertEquals("3", p3.first().id()); + } + @Test public void testRelaxedTags() { Document doc = Jsoup.parse("<abc_def id=1>Hello</abc_def> <abc-def id=2>There</abc-def>"); From bfdd534b55aef4942582acb01287ba6431a04fa0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 28 Dec 2021 17:23:06 +1100 Subject: [PATCH 703/774] Added javadoc since note --- src/main/java/org/jsoup/select/Evaluator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/select/Evaluator.java b/src/main/java/org/jsoup/select/Evaluator.java index d8d71777b6..3303684783 100644 --- a/src/main/java/org/jsoup/select/Evaluator.java +++ b/src/main/java/org/jsoup/select/Evaluator.java @@ -814,7 +814,8 @@ public String toString() { } /** - * Evaluator for matching Element (and its descendants) whole text with regex + * Evaluator for matching Element (and its descendants) whole text with regex. + * @since 1.15.1. */ public static final class MatchesWholeText extends Evaluator { private final Pattern pattern; @@ -836,7 +837,8 @@ public String toString() { } /** - * Evaluator for matching Element's own whole text with regex + * Evaluator for matching Element's own whole text with regex. + * @since 1.15.1. */ public static final class MatchesWholeOwnText extends Evaluator { private final Pattern pattern; From 6c7679ce2f00a517717962fc896d80ff5b50d4be Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 29 Dec 2021 12:46:19 +1100 Subject: [PATCH 704/774] Added default impls of tail() for Node Visitor and Filters Allows to be called via a lambda --- CHANGES | 3 +++ pom.xml | 15 ++++++++++- .../java/org/jsoup/select/NodeFilter.java | 7 +++-- .../java/org/jsoup/select/NodeVisitor.java | 7 +++-- .../java/org/jsoup/nodes/ElementTest.java | 27 +++++++++++++++++-- .../java/org/jsoup/select/TraversorTest.java | 9 +++++++ 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 39ec53602b..f5e75b3a5a 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,9 @@ jsoup changelog * Improvement: added a convenience method Jsoup.parse(File). <https://github.com/jhy/jsoup/issues/1693> + * Improvement: in the NodeTraversor, added default implementations for NodeVisitor.tail() and NodeFilter.tail(), so + that code using only head() methods can be written as lambdas. + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. <https://github.com/jhy/jsoup/issues/1656> diff --git a/pom.xml b/pom.xml index 2ab8666367..4328b12716 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ <version>3.3.1</version> <configuration> <doclint>none</doclint> - <source>7</source> + <source>8</source> </configuration> <executions> <execution> @@ -211,6 +211,19 @@ <excludes> <exclude>@java.lang.Deprecated</exclude> </excludes> + <overrideCompatibilityChangeParameters> + <!-- allows new default and move to default methods. compatible as long as existing binaries aren't making calls via reflection. if so, they need to catch errors anyway. --> + <overrideCompatibilityChangeParameter> + <compatibilityChange>METHOD_NEW_DEFAULT</compatibilityChange> + <binaryCompatible>true</binaryCompatible> + <sourceCompatible>true</sourceCompatible> + </overrideCompatibilityChangeParameter> + <overrideCompatibilityChangeParameter> + <compatibilityChange>METHOD_ABSTRACT_NOW_DEFAULT</compatibilityChange> + <binaryCompatible>true</binaryCompatible> + <sourceCompatible>true</sourceCompatible> + </overrideCompatibilityChangeParameter> + </overrideCompatibilityChangeParameters> </parameter> </configuration> <executions> diff --git a/src/main/java/org/jsoup/select/NodeFilter.java b/src/main/java/org/jsoup/select/NodeFilter.java index 9f80816485..b1a61fc5bb 100644 --- a/src/main/java/org/jsoup/select/NodeFilter.java +++ b/src/main/java/org/jsoup/select/NodeFilter.java @@ -10,7 +10,7 @@ * create a start tag for a node, and tail to create the end tag. * </p> * <p> - * For every node, the filter has to decide whether to + * For every node, the filter has to decide whether to: * <ul> * <li>continue ({@link FilterResult#CONTINUE}),</li> * <li>skip all children ({@link FilterResult#SKIP_CHILDREN}),</li> @@ -50,9 +50,12 @@ enum FilterResult { /** * Callback for when a node is last visited, after all of its descendants have been visited. + * <p>This method has a default implementation to return {@link FilterResult#CONTINUE}.</p> * @param node the node being visited. * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1. * @return Filter decision */ - FilterResult tail(Node node, int depth); + default FilterResult tail(Node node, int depth) { + return FilterResult.CONTINUE; + } } diff --git a/src/main/java/org/jsoup/select/NodeVisitor.java b/src/main/java/org/jsoup/select/NodeVisitor.java index 5f0011ad53..f6beb89d23 100644 --- a/src/main/java/org/jsoup/select/NodeVisitor.java +++ b/src/main/java/org/jsoup/select/NodeVisitor.java @@ -27,11 +27,14 @@ <p>The node may be modified (e.g. {@link Node#attr(String)} or replaced {@link N /** Callback for when a node is last visited, after all of its descendants have been visited. - <p>Note that replacement with {@link Node#replaceWith(Node)}</p> is not supported in {@code tail}. + <p>This method has a default no-op implementation.</p> + <p>Note that replacement with {@link Node#replaceWith(Node)} is not supported during {@code tail()}. @param node the node being visited. @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1. */ - void tail(Node node, int depth); + default void tail(Node node, int depth) { + // no-op by default, to allow just specifying the head() method + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 712541dbcc..96cf4b351b 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1783,6 +1783,7 @@ public void testRoot() { public void testTraverse() { Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three"); Element div = doc.selectFirst("div"); + assertNotNull(div); final AtomicInteger counter = new AtomicInteger(0); Element div2 = div.traverse(new NodeVisitor() { @@ -1802,11 +1803,24 @@ public void tail(Node node, int depth) { assertEquals(div2, div); } + @Test void testTraverseLambda() { + Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three"); + Element div = doc.selectFirst("div"); + assertNotNull(div); + final AtomicInteger counter = new AtomicInteger(0); + + Element div2 = div.traverse((node, depth) -> counter.incrementAndGet()); + + assertEquals(7, counter.get()); + assertEquals(div2, div); + } + @Test - public void voidTestFilterCallReturnsElement() { - // doesn't actually test the filter so much as the return type for Element. See node.nodeFilter for an acutal test + public void testFilterCallReturnsElement() { + // doesn't actually test the filter so much as the return type for Element. See node.nodeFilter for an actual test Document doc = Jsoup.parse("<div><p>One<p>Two<p>Three"); Element div = doc.selectFirst("div"); + assertNotNull(div); Element div2 = div.filter(new NodeFilter() { @Override public FilterResult head(Node node, int depth) { @@ -1822,6 +1836,15 @@ public FilterResult tail(Node node, int depth) { assertSame(div, div2); } + @Test void testFilterAsLambda() { + Document doc = Jsoup.parse("<div><p>One<p id=2>Two<p>Three"); + doc.filter((node, depth) -> node.attr("id").equals("2") + ? NodeFilter.FilterResult.REMOVE + : NodeFilter.FilterResult.CONTINUE); + + assertEquals("<div><p>One</p><p>Three</p></div>", TextUtil.stripNewlines(doc.body().html())); + } + @Test public void doesntDeleteZWJWhenNormalizingText() { String text = "\uD83D\uDC69\u200D\uD83D\uDCBB\uD83E\uDD26\uD83C\uDFFB\u200D\u2642\uFE0F"; diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java index c5d8a2f201..98addcbeb7 100644 --- a/src/test/java/org/jsoup/select/TraversorTest.java +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -4,6 +4,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -171,4 +172,12 @@ public void tail(Node node, int depth) { " <p><span>2</span><span>3</span></p>\n" + "</div>", doc.body().html()); } + + @Test public void canSpecifyOnlyHead() { + // really, a compilation test - works as a lambda if just head + Document doc = Jsoup.parse("<div><p>One</p></div>"); + final int[] count = {0}; + NodeTraversor.traverse((node, depth) -> count[0]++, doc); + assertEquals(7, count[0]); + } } From 2bad736e9a18bab0b4660423b0eb3c4dc961a312 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 29 Dec 2021 15:01:54 +1100 Subject: [PATCH 705/774] Allow nodes to be removed in NodeTraversor --- CHANGES | 2 ++ .../java/org/jsoup/select/NodeTraversor.java | 21 ++++++++++++++----- .../java/org/jsoup/select/NodeVisitor.java | 10 ++++----- .../java/org/jsoup/select/TraversorTest.java | 13 ++++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index f5e75b3a5a..c66b9ae554 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,8 @@ jsoup changelog * Improvement: in the NodeTraversor, added default implementations for NodeVisitor.tail() and NodeFilter.tail(), so that code using only head() methods can be written as lambdas. + * Improvement: in NodeTraversor, added support for removing nodes via Node.remove() during NodeVisitor.head(). + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. <https://github.com/jhy/jsoup/issues/1656> diff --git a/src/main/java/org/jsoup/select/NodeTraversor.java b/src/main/java/org/jsoup/select/NodeTraversor.java index ae2b458002..5b01a2f490 100644 --- a/src/main/java/org/jsoup/select/NodeTraversor.java +++ b/src/main/java/org/jsoup/select/NodeTraversor.java @@ -20,16 +20,27 @@ public class NodeTraversor { public static void traverse(NodeVisitor visitor, Node root) { Validate.notNull(visitor); Validate.notNull(root); - Node node = root; - Node parent; // remember parent to find nodes that get replaced in .head int depth = 0; while (node != null) { - parent = node.parentNode(); + Node parent = node.parentNode(); // remember parent to find nodes that get replaced in .head + int origSize = parent != null ? parent.childNodeSize() : 0; + Node next = node.nextSibling(); + visitor.head(node, depth); // visit current node - if (parent != null && !node.hasParent()) // must have been replaced; find replacement - node = parent.childNode(node.siblingIndex()); // replace ditches parent but keeps sibling index + if (parent != null && !node.hasParent()) { // removed or replaced + if (origSize == parent.childNodeSize()) { // replaced + node = parent.childNode(node.siblingIndex()); // replace ditches parent but keeps sibling index + } else { // removed + node = next; + if (node == null) { // last one, go up + node = parent; + depth--; + } + continue; // don't tail removed + } + } if (node.childNodeSize() > 0) { // descend node = node.childNode(0); diff --git a/src/main/java/org/jsoup/select/NodeVisitor.java b/src/main/java/org/jsoup/select/NodeVisitor.java index f6beb89d23..0dbdc86ce8 100644 --- a/src/main/java/org/jsoup/select/NodeVisitor.java +++ b/src/main/java/org/jsoup/select/NodeVisitor.java @@ -14,10 +14,9 @@ public interface NodeVisitor { /** Callback for when a node is first visited. - <p>The node may be modified (e.g. {@link Node#attr(String)} or replaced {@link Node#replaceWith(Node)}). If it's - {@code instanceOf Element}, you may cast it to an {@link Element} and access those methods.</p> - <p>Note that nodes may not be removed during traversal using this method; use {@link - NodeTraversor#filter(NodeFilter, Node)} with a {@link NodeFilter.FilterResult#REMOVE} return instead.</p> + <p>The node may be modified (e.g. {@link Node#attr(String)}, replaced {@link Node#replaceWith(Node)}) or removed + {@link Node#remove()}. If it's {@code instanceOf Element}, you may cast it to an {@link Element} and access those + methods.</p> @param node the node being visited. @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node @@ -28,7 +27,8 @@ <p>The node may be modified (e.g. {@link Node#attr(String)} or replaced {@link N /** Callback for when a node is last visited, after all of its descendants have been visited. <p>This method has a default no-op implementation.</p> - <p>Note that replacement with {@link Node#replaceWith(Node)} is not supported during {@code tail()}. + <p>Note that neither replacement with {@link Node#replaceWith(Node)} nor removal with {@link Node#remove()} is + supported during {@code tail()}. @param node the node being visited. @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java index 98addcbeb7..8d0667e648 100644 --- a/src/test/java/org/jsoup/select/TraversorTest.java +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -1,6 +1,7 @@ package org.jsoup.select; import org.jsoup.Jsoup; +import org.jsoup.TextUtil; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; @@ -180,4 +181,16 @@ public void tail(Node node, int depth) { NodeTraversor.traverse((node, depth) -> count[0]++, doc); assertEquals(7, count[0]); } + + @Test public void canRemoveDuringHead() { + Document doc = Jsoup.parse("<div><p id=1>Zero<p id=1>One<p id=2>Two<p>Three</div>"); + NodeTraversor.traverse((node, depth) -> { + if (node.attr("id").equals("1")) + node.remove(); + else if (node instanceof TextNode && ((TextNode) node).text().equals("Three")) + node.remove(); + }, doc); + + assertEquals("<div><p id=\"2\">Two</p><p></p></div>", TextUtil.stripNewlines(doc.body().html())); + } } From e64a03a54c03f4c7c9389e2aa5214878927c564d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 29 Dec 2021 15:05:35 +1100 Subject: [PATCH 706/774] Added issue URL for NodeTraversor change --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index c66b9ae554..000ebc0f7f 100644 --- a/CHANGES +++ b/CHANGES @@ -41,6 +41,7 @@ jsoup changelog that code using only head() methods can be written as lambdas. * Improvement: in NodeTraversor, added support for removing nodes via Node.remove() during NodeVisitor.head(). + <https://github.com/jhy/jsoup/issues/1699> * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. From b236d07ee7893c39e45421f79e220fdee0d90e32 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 29 Dec 2021 15:29:17 +1100 Subject: [PATCH 707/774] Added Node.forEachNode and Element.forEach consumer methods Fixes #1700 --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Element.java | 24 ++++++++++++++++++- src/main/java/org/jsoup/nodes/Node.java | 14 +++++++++++ .../java/org/jsoup/nodes/ElementTest.java | 13 ++++++++++ src/test/java/org/jsoup/nodes/NodeTest.java | 15 ++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 000ebc0f7f..2963ffece3 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,10 @@ jsoup changelog * Improvement: in NodeTraversor, added support for removing nodes via Node.remove() during NodeVisitor.head(). <https://github.com/jhy/jsoup/issues/1699> + * Improvement: added Node.forEachNode(Consumer<Node>) and Element.forEach(Consumer<Element) methods, to efficiently + traverse the DOM with a functional interface. + <https://github.com/jhy/jsoup/issues/1700> + * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve case. <https://github.com/jhy/jsoup/issues/1656> diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index f96582f490..c350eb3aeb 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -1735,7 +1736,28 @@ public Element root() { @Override public Element traverse(NodeVisitor nodeVisitor) { - return (Element) super.traverse(nodeVisitor); + return (Element) super.traverse(nodeVisitor); + } + + @Override + public Element forEachNode(Consumer<? super Node> action) { + return (Element) super.forEachNode(action); + } + + /** + Perform the supplied action on this Element and each of its descendant Elements, during a depth-first traversal. + Elements may be inspected, changed, added, replaced, or removed. + @param action the function to perform on the element + @return this Element, for chaining + @see Node#forEachNode(Consumer) + */ + public Element forEach(Consumer<? super Element> action) { + Validate.notNull(action); + NodeTraversor.traverse((node, depth) -> { + if (node instanceof Element) + action.accept((Element) node); + }, this); + return this; } @Override diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index e0520ebee5..381edeea4c 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -15,6 +15,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.function.Consumer; /** The base, abstract Node model. Elements, Documents, Comments etc are all Node instances. @@ -626,6 +627,19 @@ public Node traverse(NodeVisitor nodeVisitor) { return this; } + /** + Perform the supplied action on this Node and each of its descendants, during a depth-first traversal. Nodes may be + inspected, changed, added, replaced, or removed. + @param action the function to perform on the node + @return this Node, for chaining + @see Element#forEach(Consumer) + */ + public Node forEachNode(Consumer<? super Node> action) { + Validate.notNull(action); + NodeTraversor.traverse((node, depth) -> action.accept(node), this); + return this; + } + /** * Perform a depth-first filtering through this node and its descendants. * @param nodeFilter the filter callbacks to perform on each node diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 96cf4b351b..7d8c0af0e2 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -1845,6 +1845,19 @@ public FilterResult tail(Node node, int depth) { assertEquals("<div><p>One</p><p>Three</p></div>", TextUtil.stripNewlines(doc.body().html())); } + @Test void testForEach() { + Document doc = Jsoup.parse("<div><p>Hello</p></div><div>There</div><div id=1>Gone<p></div>"); + doc.forEach(el -> { + if (el.id().equals("1")) + el.remove(); + else if (el.text().equals("There")) { + el.text("There Now"); + el.append("<p>Another</p>"); + } + }); + assertEquals("<div><p>Hello</p></div><div>There Now<p>Another</p></div>", TextUtil.stripNewlines(doc.body().html())); + } + @Test public void doesntDeleteZWJWhenNormalizingText() { String text = "\uD83D\uDC69\u200D\uD83D\uDCBB\uD83E\uDD26\uD83C\uDFFB\u200D\u2642\uFE0F"; diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index fc8aaa7fce..af9db4d9a1 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -250,6 +250,21 @@ public void tail(Node node, int depth) { assertEquals("<div><p><#text></#text></p></div>", accum.toString()); } + @Test public void forEachNode() { + Document doc = Jsoup.parse("<div><p>Hello</p></div><div>There</div><div id=1>Gone<p></div>"); + doc.forEachNode(node -> { + if (node instanceof TextNode) { + TextNode textNode = (TextNode) node; + if (textNode.text().equals("There")) { + textNode.text("There Now"); + textNode.after("<p>Another"); + } + } else if (node.attr("id").equals("1")) + node.remove(); + }); + assertEquals("<div><p>Hello</p></div><div>There Now<p>Another</p></div>", TextUtil.stripNewlines(doc.body().html())); + } + @Test public void orphanNodeReturnsNullForSiblingElements() { Node node = new Element(Tag.valueOf("p"), ""); Element el = new Element(Tag.valueOf("p"), ""); From 180fe95df9519d2db39c3764a4c2116c4275c153 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 29 Dec 2021 15:50:52 +1100 Subject: [PATCH 708/774] Added a project Consumer interface As I forgot that Android API 10 did not include the Java functional Consumer interface. --- src/main/java/org/jsoup/helper/Consumer.java | 16 ++++++++++++++++ src/main/java/org/jsoup/nodes/Element.java | 2 +- src/main/java/org/jsoup/nodes/Node.java | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/jsoup/helper/Consumer.java diff --git a/src/main/java/org/jsoup/helper/Consumer.java b/src/main/java/org/jsoup/helper/Consumer.java new file mode 100644 index 0000000000..6200a68b88 --- /dev/null +++ b/src/main/java/org/jsoup/helper/Consumer.java @@ -0,0 +1,16 @@ +package org.jsoup.helper; + +/** + A functional interface (ala Java's {@link java.util.function.Consumer} interface, implemented here for cross compatibility with Android. + @param <T> the input type + */ +@FunctionalInterface +public interface Consumer<T> { + + /** + * Executre this operation on the supplied argument. It is expected to have side effects. + * + * @param t the input argument + */ + void accept(T t); +} diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index c350eb3aeb..911732b4eb 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.helper.ChangeNotifyingArrayList; +import org.jsoup.helper.Consumer; import org.jsoup.helper.Validate; import org.jsoup.internal.NonnullByDefault; import org.jsoup.internal.StringUtil; @@ -26,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 381edeea4c..993139579f 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -1,6 +1,7 @@ package org.jsoup.nodes; import org.jsoup.SerializationException; +import org.jsoup.helper.Consumer; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; import org.jsoup.select.NodeFilter; @@ -15,7 +16,6 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.function.Consumer; /** The base, abstract Node model. Elements, Documents, Comments etc are all Node instances. From ddfdc5b56fe0b7e8cb784c4617fd100adf9ca492 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 29 Dec 2021 15:52:45 +1100 Subject: [PATCH 709/774] Fix typo in javadoc --- src/main/java/org/jsoup/helper/Consumer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/Consumer.java b/src/main/java/org/jsoup/helper/Consumer.java index 6200a68b88..0ded06e7bd 100644 --- a/src/main/java/org/jsoup/helper/Consumer.java +++ b/src/main/java/org/jsoup/helper/Consumer.java @@ -8,7 +8,7 @@ A functional interface (ala Java's {@link java.util.function.Consumer} interface public interface Consumer<T> { /** - * Executre this operation on the supplied argument. It is expected to have side effects. + * Execute this operation on the supplied argument. It is expected to have side effects. * * @param t the input argument */ From 4d0f93d090719b87a1d26b100c66ba69bcc80951 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 2 Jan 2022 10:43:20 +1100 Subject: [PATCH 710/774] =?UTF-8?q?=E2=98=80=EF=B8=8FHappy=20New=20Year=20?= =?UTF-8?q?2022!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d704b1265e..fef8197fe2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2009-2021 Jonathan Hedley <https://jsoup.org/> +Copyright (c) 2009-2022 Jonathan Hedley <https://jsoup.org/> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f24175e49e4359631cba640f7effaf5327094619 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 2 Jan 2022 13:50:45 +1100 Subject: [PATCH 711/774] When serializing TextNodes, skip emitting blank newlines if the previous element indented Also, don't add newlines in outline mode for data nodes Fixes #1688 Fixes #1689 --- CHANGES | 5 ++ src/main/java/org/jsoup/nodes/Element.java | 8 +- src/main/java/org/jsoup/nodes/TextNode.java | 7 +- .../java/org/jsoup/nodes/ElementTest.java | 47 +++++++++++- .../java/org/jsoup/parser/HtmlParserTest.java | 10 +-- .../parser/HtmlTreeBuilderStateTest.java | 76 +++++++++---------- src/test/resources/htmltests/medium.html | 20 ++--- 7 files changed, 115 insertions(+), 58 deletions(-) diff --git a/CHANGES b/CHANGES index 2963ffece3..50c1fa4563 100644 --- a/CHANGES +++ b/CHANGES @@ -62,6 +62,11 @@ jsoup changelog that could throw an IllegalFormatException. <https://github.com/jhy/jsoup/issues/1691> + * Bugfix: when serializing HTML with Pretty Print enabled, extraneous whitespace may be added on closing tags, or + extra newlines may be added at the end of script blocks. + <https://github.com/jhy/jsoup/issues/1688> + <https://github.com/jhy/jsoup/issues/1689> + * Bugfix [Fuzz]: speed improvement when parsing constructed HTML containing very deeply incorrectly stacked formatting elements with many attributes. <https://github.com/jhy/jsoup/issues/1695> diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 911732b4eb..44d7a456f6 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1624,8 +1624,12 @@ public Element val(String value) { return this; } + boolean shouldIndent(final Document.OutputSettings out) { + return out.prettyPrint() && isFormatAsBlock(out) && !isInlineable(out); + } + void outerHtmlHead(final Appendable accum, int depth, final Document.OutputSettings out) throws IOException { - if (out.prettyPrint() && isFormatAsBlock(out) && !isInlineable(out)) { + if (shouldIndent(out)) { if (accum instanceof StringBuilder) { if (((StringBuilder) accum).length() > 0) indent(accum, depth, out); @@ -1650,7 +1654,7 @@ void outerHtmlHead(final Appendable accum, int depth, final Document.OutputSetti void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException { if (!(childNodes.isEmpty() && tag.isSelfClosing())) { if (out.prettyPrint() && (!childNodes.isEmpty() && ( - tag.formatAsBlock() || (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && !(childNodes.get(0) instanceof TextNode)))) + tag.formatAsBlock() || (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && (childNodes.get(0) instanceof Element)))) ))) indent(accum, depth, out); accum.append("</").append(tagName()).append('>'); diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 45794a6aa3..191ddadece 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -82,7 +82,12 @@ public TextNode splitText(int offset) { void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { final boolean prettyPrint = out.prettyPrint(); - if (prettyPrint && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) + Element parent = parentNode instanceof Element ? ((Element) parentNode) : null; + boolean parentIndent = parent != null && parent.shouldIndent(out); + if (parentIndent && getWholeText().startsWith("\n") && isBlank()) // we are skippable whitespace + return; + + if (prettyPrint && ((siblingIndex() == 0 && parent != null && parent.tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) indent(accum, depth, out); final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 7d8c0af0e2..c5632920a2 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -12,6 +12,8 @@ import org.jsoup.select.NodeVisitor; import org.jsoup.select.QueryParser; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.Collection; @@ -21,6 +23,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -525,7 +528,7 @@ public void testEmptyElementFormatHtml() { public void testNoIndentOnScriptAndStyle() { // don't newline+indent closing </script> and </style> tags Document doc = Jsoup.parse("<script>one\ntwo</script>\n<style>three\nfour</style>"); - assertEquals("<script>one\ntwo</script> \n<style>three\nfour</style>", doc.head().html()); + assertEquals("<script>one\ntwo</script>\n<style>three\nfour</style>", doc.head().html()); } @Test @@ -2162,4 +2165,44 @@ public void childNodesAccessorDoesNotVivify() { assertEquals(1, docClone.children().size()); // check did not get the second div as the owner's children assertEquals(divClone, docClone.child(0)); // note not the head or the body -- not normalized } -} + + private static Stream<Document.OutputSettings> testOutputSettings() { + return Stream.of( + new Document.OutputSettings().prettyPrint(true).indentAmount(4), + new Document.OutputSettings().prettyPrint(true).indentAmount(1), + new Document.OutputSettings().prettyPrint(true).indentAmount(4).outline(true), + new Document.OutputSettings().prettyPrint(false) + ); + } + + @ParameterizedTest + @MethodSource("testOutputSettings") + void prettySerializationRoundTrips(Document.OutputSettings settings) { + // https://github.com/jhy/jsoup/issues/1688 + // tests that repeated html() and parse() does not accumulate errant spaces / newlines + Document doc = Jsoup.parse("<div>\nFoo\n<p>\nBar\nqux</p></div>\n<script>\n alert('Hello!');\n</script>"); + doc.outputSettings(settings); + String html = doc.html(); + Document doc2 = Jsoup.parse(html); + doc2.outputSettings(settings); + String html2 = doc2.html(); + + assertEquals(html, html2); + } + + @Test void prettyPrintScriptsDoesNotGrowOnRepeat() { + Document doc = Jsoup.parse("<div>\nFoo\n<p>\nBar\nqux</p></div>\n<script>\n alert('Hello!');\n</script>"); + Document.OutputSettings settings = doc.outputSettings(); + settings + .prettyPrint(true) + .outline(true) + .indentAmount(4) + ; + + String html = doc.html(); + Document doc2 = Jsoup.parse(html); + doc2.outputSettings(settings); + String html2 = doc2.html(); + assertEquals(html, html2); + } +} \ No newline at end of file diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 5214f1cbaf..fd0f1d14ca 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1088,10 +1088,10 @@ public void testInvalidTableContents() throws IOException { "<script type=\"text/javascript\">console.log('bar');</script>"; Document body = Jsoup.parseBodyFragment(html); - assertEquals("<script type=\"text/javascript\">console.log('foo');</script> \n" + + assertEquals("<script type=\"text/javascript\">console.log('foo');</script>\n" + "<div id=\"somecontent\">\n" + " some content\n" + - "</div> \n" + + "</div>\n" + "<script type=\"text/javascript\">console.log('bar');</script>", body.body().html()); } @@ -1246,7 +1246,7 @@ public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/comments.html"); Document doc = Jsoup.parse(in, "UTF-8"); - assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", + assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><!-- now --> <head><!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", StringUtil.normaliseWhitespace(doc.html())); assertEquals("A Certain Kind of Test", doc.head().select("title").text()); @@ -1487,7 +1487,7 @@ private boolean didAddElements(String input) { String html = "<a>\n<b>\n<div>\n<a>test</a>\n</div>\n</b>\n</a>"; Document doc = Jsoup.parse(html); assertNotNull(doc); - assertEquals("<a> <b> </b></a><b><div><a> </a><a>test</a> </div> </b>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<a><b> </b></a><b><div><a></a><a>test</a></div> </b>", TextUtil.stripNewlines(doc.body().html())); } @Test public void tagsMustStartWithAscii() { @@ -1633,4 +1633,4 @@ private boolean didAddElements(String input) { assertEquals("<template><select></select><input>&lt;</template>", TextUtil.stripNewlines(doc.head().html())); } -} +} \ No newline at end of file diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index 3011609f1f..e92c610ab3 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -64,50 +64,50 @@ public void ensureArraysAreSorted() { @Test public void nestedAnchorElements01() { String html = "<html>\n" + - " <body>\n" + - " <a href='#1'>\n" + - " <div>\n" + - " <a href='#2'>child</a>\n" + - " </div>\n" + - " </a>\n" + - " </body>\n" + - "</html>"; + " <body>\n" + + " <a href='#1'>\n" + + " <div>\n" + + " <a href='#2'>child</a>\n" + + " </div>\n" + + " </a>\n" + + " </body>\n" + + "</html>"; String s = Jsoup.parse(html).toString(); - assertEquals("<html> \n" + - " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body> <a href=\"#1\"> </a>\n" + - " <div>\n" + - " <a href=\"#1\"> </a><a href=\"#2\">child</a> \n" + - " </div> \n" + - " </body>\n" + - "</html>", s); + assertEquals("<html>\n" + + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + + " <body><a href=\"#1\"> </a>\n" + + " <div>\n" + + " <a href=\"#1\"></a><a href=\"#2\">child</a>\n" + + " </div>\n" + + " </body>\n" + + "</html>", s); } @Test public void nestedAnchorElements02() { String html = "<html>\n" + - " <body>\n" + - " <a href='#1'>\n" + - " <div>\n" + - " <div>\n" + - " <a href='#2'>child</a>\n" + - " </div>\n" + - " </div>\n" + - " </a>\n" + - " </body>\n" + - "</html>"; + " <body>\n" + + " <a href='#1'>\n" + + " <div>\n" + + " <div>\n" + + " <a href='#2'>child</a>\n" + + " </div>\n" + + " </div>\n" + + " </a>\n" + + " </body>\n" + + "</html>"; String s = Jsoup.parse(html).toString(); - assertEquals("<html> \n" + - " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body> <a href=\"#1\"> </a>\n" + - " <div>\n" + - " <a href=\"#1\"> </a>\n" + - " <div>\n" + - " <a href=\"#1\"> </a><a href=\"#2\">child</a> \n" + - " </div> \n" + - " </div> \n" + - " </body>\n" + - "</html>", s); + assertEquals("<html>\n" + + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + + " <body><a href=\"#1\"> </a>\n" + + " <div>\n" + + " <a href=\"#1\"></a>\n" + + " <div>\n" + + " <a href=\"#1\"></a><a href=\"#2\">child</a>\n" + + " </div>\n" + + " </div>\n" + + " </body>\n" + + "</html>", s); } -} +} \ No newline at end of file diff --git a/src/test/resources/htmltests/medium.html b/src/test/resources/htmltests/medium.html index 10f4402ffc..b228f2ae69 100644 --- a/src/test/resources/htmltests/medium.html +++ b/src/test/resources/htmltests/medium.html @@ -3,16 +3,16 @@ <title>Large HTML</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> - <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero.</p> <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> - <p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p> - <p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p> - <p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p> - <p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p> - <p><i>Proin sodales libero eget ante</i>. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p> - <p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p> - <p>Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Suspendisse potenti</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> - <p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p> - <p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p> + <p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna.</p> + <p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi.</p> + <p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque.</p> + <p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum.</p> + <p><i>Proin sodales libero eget ante</i>. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis.</p> + <p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet.</p> + <p>Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Suspendisse potenti</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue.</p> + <p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam.</p> + <p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio.</p> </body> </html> \ No newline at end of file From 22f05e8fbbc29e05d678de05dd0913529fbd1a37 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 2 Jan 2022 14:03:12 +1100 Subject: [PATCH 712/774] Test BRs are treated as newlines in wholeText For #1437 Was fixed by #1636 --- src/test/java/org/jsoup/nodes/ElementTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index c5632920a2..f1a6556f71 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2205,4 +2205,15 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { String html2 = doc2.html(); assertEquals(html, html2); } + + @Test void elementBrText() { + // testcase for https://github.com/jhy/jsoup/issues/1437 + String html = "<p>Hello<br>World</p>"; + Document doc = Jsoup.parse(html); + Element p = doc.select("p").first(); + assertNotNull(p); + assertEquals(html, p.outerHtml()); + assertEquals("Hello World", p.text()); + assertEquals("Hello\nWorld", p.wholeText()); + } } \ No newline at end of file From 59201d92f5c5e44b8a6b19fab60fd80495e705cb Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 2 Jan 2022 16:32:11 +1100 Subject: [PATCH 713/774] Simplify traverse to lambda --- src/main/java/org/jsoup/nodes/Element.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 44d7a456f6..5d9279671d 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1284,14 +1284,7 @@ public void tail(Node node, int depth) { */ public String wholeText() { final StringBuilder accum = StringUtil.borrowBuilder(); - NodeTraversor.traverse(new NodeVisitor() { - public void head(Node node, int depth) { - appendWholeText(node, accum); - } - - public void tail(Node node, int depth) {} - }, this); - + NodeTraversor.traverse((node, depth) -> appendWholeText(node, accum), this); return StringUtil.releaseBuilder(accum); } From d8fff2de1676e50557a1d6356e6599b6df180598 Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 2 Jan 2022 16:32:55 +1100 Subject: [PATCH 714/774] Add override annotations --- src/main/java/org/jsoup/nodes/Attribute.java | 2 ++ src/main/java/org/jsoup/select/StructuralEvaluator.java | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 5cdecc339c..4106bd8a0f 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -56,6 +56,7 @@ public Attribute(String key, @Nullable String val, @Nullable Attributes parent) Get the attribute key. @return the attribute key */ + @Override public String getKey() { return key; } @@ -80,6 +81,7 @@ public void setKey(String key) { Get the attribute value. Will return an empty string if the value is not set. @return the attribute value */ + @Override public String getValue() { return Attributes.checkNotNull(val); } diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index 7d13ab6610..52d8697ae2 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -10,6 +10,7 @@ abstract class StructuralEvaluator extends Evaluator { Evaluator evaluator; static class Root extends Evaluator { + @Override public boolean matches(Element root, Element element) { return root == element; } @@ -23,6 +24,7 @@ public Has(Evaluator evaluator) { finder = new Collector.FirstFinder(evaluator); } + @Override public boolean matches(Element root, Element element) { // for :has, we only want to match children (or below), not the input element. And we want to minimize GCs for (int i = 0; i < element.childNodeSize(); i++) { @@ -47,6 +49,7 @@ public Not(Evaluator evaluator) { this.evaluator = evaluator; } + @Override public boolean matches(Element root, Element node) { return !evaluator.matches(root, node); } @@ -62,6 +65,7 @@ public Parent(Evaluator evaluator) { this.evaluator = evaluator; } + @Override public boolean matches(Element root, Element element) { if (root == element) return false; @@ -88,6 +92,7 @@ public ImmediateParent(Evaluator evaluator) { this.evaluator = evaluator; } + @Override public boolean matches(Element root, Element element) { if (root == element) return false; @@ -107,6 +112,7 @@ public PreviousSibling(Evaluator evaluator) { this.evaluator = evaluator; } + @Override public boolean matches(Element root, Element element) { if (root == element) return false; @@ -133,6 +139,7 @@ public ImmediatePreviousSibling(Evaluator evaluator) { this.evaluator = evaluator; } + @Override public boolean matches(Element root, Element element) { if (root == element) return false; From 3d136d78a0634dbe934b0cce8411c5ea7f1913fc Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 2 Jan 2022 16:41:55 +1100 Subject: [PATCH 715/774] Override clone() To prevent warnings --- src/main/java/org/jsoup/parser/ParseErrorList.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/jsoup/parser/ParseErrorList.java b/src/main/java/org/jsoup/parser/ParseErrorList.java index a1c6227716..969c862d83 100644 --- a/src/main/java/org/jsoup/parser/ParseErrorList.java +++ b/src/main/java/org/jsoup/parser/ParseErrorList.java @@ -41,4 +41,10 @@ public static ParseErrorList noTracking() { public static ParseErrorList tracking(int maxSize) { return new ParseErrorList(INITIAL_CAPACITY, maxSize); } + + @Override + public Object clone() { + // all class fields are primitive, so native clone is enough. + return super.clone(); + } } From f4d00c290325935186f75f1206d16397cc157ced Mon Sep 17 00:00:00 2001 From: jhy <jonathan@hedley.net> Date: Sun, 2 Jan 2022 17:13:06 +1100 Subject: [PATCH 716/774] Simplified TextNode.outerHtml a little --- src/main/java/org/jsoup/internal/StringUtil.java | 13 ++++++++++++- src/main/java/org/jsoup/nodes/LeafNode.java | 3 +-- src/main/java/org/jsoup/nodes/TextNode.java | 14 ++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 1882f2a9f1..8c5e50b87f 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -147,7 +147,7 @@ public static String padding(int width, int maxPaddingWidth) { * @param string string to test * @return if string is blank */ - public static boolean isBlank(String string) { + public static boolean isBlank(final String string) { if (string == null || string.length() == 0) return true; @@ -159,6 +159,17 @@ public static boolean isBlank(String string) { return true; } + /** + Tests if a string starts with a newline character + @param string string to test + @return if its first character is a newline + */ + public static boolean startsWithNewline(final String string) { + if (string == null || string.length() == 0) + return false; + return string.charAt(0) == '\n'; + } + /** * Tests if a string is numeric, i.e. contains only digit characters * @param string string to test diff --git a/src/main/java/org/jsoup/nodes/LeafNode.java b/src/main/java/org/jsoup/nodes/LeafNode.java index 63eb460d6c..c5e3c577b7 100644 --- a/src/main/java/org/jsoup/nodes/LeafNode.java +++ b/src/main/java/org/jsoup/nodes/LeafNode.java @@ -38,9 +38,8 @@ void coreValue(String value) { @Override public String attr(String key) { - Validate.notNull(key); if (!hasAttributes()) { - return key.equals(nodeName()) ? (String) value : EmptyString; + return nodeName().equals(key) ? (String) value : EmptyString; } return super.attr(key); } diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index 191ddadece..a7e3cd51fe 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -74,20 +74,22 @@ public TextNode splitText(int offset) { String tail = text.substring(offset); text(head); TextNode tailNode = new TextNode(tail); - if (parent() != null) - parent().addChildren(siblingIndex()+1, tailNode); + if (parentNode != null) + parentNode.addChildren(siblingIndex()+1, tailNode); return tailNode; } void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { final boolean prettyPrint = out.prettyPrint(); - Element parent = parentNode instanceof Element ? ((Element) parentNode) : null; - boolean parentIndent = parent != null && parent.shouldIndent(out); - if (parentIndent && getWholeText().startsWith("\n") && isBlank()) // we are skippable whitespace + final Element parent = parentNode instanceof Element ? ((Element) parentNode) : null; + final boolean parentIndent = parent != null && parent.shouldIndent(out); + final boolean blank = isBlank(); + + if (parentIndent && StringUtil.startsWithNewline(coreValue()) && blank) // we are skippable whitespace return; - if (prettyPrint && ((siblingIndex() == 0 && parent != null && parent.tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) + if (prettyPrint && ((siblingIndex == 0 && parent != null && parent.tag().formatAsBlock() && !blank) || (out.outline() && siblingNodes().size()>0 && !blank) )) indent(accum, depth, out); final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); From 0b66d9e79e1d37be0b32bb198ccc49b36925a9c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:32:50 +1000 Subject: [PATCH 717/774] Bump maven-bundle-plugin from 5.1.3 to 5.1.5 (#1760) Bumps maven-bundle-plugin from 5.1.3 to 5.1.5. --- updated-dependencies: - dependency-name: org.apache.felix:maven-bundle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4328b12716..db9e963067 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> - <version>5.1.3</version> + <version>5.1.5</version> <executions> <execution> <id>bundle-manifest</id> From 3a46fd918cbf06c3101453cb535518418d8904b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:33:38 +1000 Subject: [PATCH 718/774] Bump jetty-server from 9.4.44.v20210927 to 9.4.46.v20220331 (#1739) Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.44.v20210927 to 9.4.46.v20220331. - [Release notes](https://github.com/eclipse/jetty.project/releases) - [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.44.v20210927...jetty-9.4.46.v20220331) --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index db9e963067..1ee6a417ff 100644 --- a/pom.xml +++ b/pom.xml @@ -333,7 +333,7 @@ <!-- jetty for webserver integration tests. 9.x is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.44.v20210927</version> + <version>9.4.46.v20220331</version> <scope>test</scope> </dependency> From e180aa400257f3a832f42993f753dc71a8f794e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:34:58 +1000 Subject: [PATCH 719/774] Bump maven-jar-plugin from 3.2.0 to 3.2.2 (#1705) Bumps [maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.2.0 to 3.2.2. - [Release notes](https://github.com/apache/maven-jar-plugin/releases) - [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.2.0...maven-jar-plugin-3.2.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-jar-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1ee6a417ff..1aeb376153 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.2.0</version> + <version>3.2.2</version> <configuration> <archive> <manifestEntries> From fdf4b783603eb067a1e365a06f381164b6e00273 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:35:26 +1000 Subject: [PATCH 720/774] Bump animal-sniffer-maven-plugin from 1.20 to 1.21 (#1713) Bumps [animal-sniffer-maven-plugin](https://github.com/mojohaus/animal-sniffer) from 1.20 to 1.21. - [Release notes](https://github.com/mojohaus/animal-sniffer/releases) - [Commits](https://github.com/mojohaus/animal-sniffer/compare/animal-sniffer-parent-1.20...animal-sniffer-parent-1.21) --- updated-dependencies: - dependency-name: org.codehaus.mojo:animal-sniffer-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1aeb376153..03d0ff023d 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ <!-- Ensure Java 8 and Android 10 API compatibility --> <groupId>org.codehaus.mojo</groupId> <artifactId>animal-sniffer-maven-plugin</artifactId> - <version>1.20</version> + <version>1.21</version> <executions> <execution> <id>animal-sniffer</id> From cb9fea2a83f131dbfb1cef3f9433596b358888cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:35:44 +1000 Subject: [PATCH 721/774] Bump japicmp-maven-plugin from 0.15.4 to 0.15.7 (#1729) Bumps [japicmp-maven-plugin](https://github.com/siom79/japicmp) from 0.15.4 to 0.15.7. - [Release notes](https://github.com/siom79/japicmp/releases) - [Changelog](https://github.com/siom79/japicmp/blob/master/release.py) - [Commits](https://github.com/siom79/japicmp/compare/japicmp-base-0.15.4...japicmp-base-0.15.7) --- updated-dependencies: - dependency-name: com.github.siom79.japicmp:japicmp-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 03d0ff023d..bcd2d5a7bb 100644 --- a/pom.xml +++ b/pom.xml @@ -192,7 +192,7 @@ <!-- API version compat check - https://siom79.github.io/japicmp/ --> <groupId>com.github.siom79.japicmp</groupId> <artifactId>japicmp-maven-plugin</artifactId> - <version>0.15.4</version> + <version>0.15.7</version> <configuration> <!-- hard code previous version; can't detect when running stateless on build server --> <oldVersion> From 768b6cf31d2f7bb931312f9588c440bcd946eb15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:36:21 +1000 Subject: [PATCH 722/774] Bump maven-compiler-plugin from 3.8.1 to 3.10.1 (#1732) Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.8.1 to 3.10.1. - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.1...maven-compiler-plugin-3.10.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bcd2d5a7bb..c6a9062e4a 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.8.1</version> + <version>3.10.1</version> <configuration> <source>1.8</source> <target>1.8</target> From e6dc745d209391ae4cec8371b9b189b36f4b8987 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:46:19 +1000 Subject: [PATCH 723/774] Bump maven-javadoc-plugin from 3.3.1 to 3.4.0 (#1772) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.1...maven-javadoc-plugin-3.4.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c6a9062e4a..856edfbffd 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.3.1</version> + <version>3.4.0</version> <configuration> <doclint>none</doclint> <source>8</source> From f881e81d83cc413fa90a1af6579072a316df6784 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:46:48 +1000 Subject: [PATCH 724/774] Bump maven-failsafe-plugin from 3.0.0-M5 to 3.0.0-M6 (#1771) Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M5 to 3.0.0-M6. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M5...surefire-3.0.0-M6) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 856edfbffd..c7229857a3 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> - <version>3.0.0-M5</version> + <version>3.0.0-M6</version> <executions> <execution> <goals> @@ -296,7 +296,7 @@ <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> - <version>3.0.0-M5</version> + <version>3.0.0-M6</version> <executions> <execution> <goals> From 3cc012dbaa0c5af34f41ece6aed1ff622a03e65d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:47:07 +1000 Subject: [PATCH 725/774] Bump maven-surefire-plugin from 3.0.0-M5 to 3.0.0-M6 (#1770) Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M5 to 3.0.0-M6. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M5...surefire-3.0.0-M6) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c7229857a3..55ab982c26 100644 --- a/pom.xml +++ b/pom.xml @@ -166,7 +166,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>3.0.0-M5</version> + <version>3.0.0-M6</version> <configuration> <!-- smaller stack to find stack overflows --> <argLine>-Xss256k</argLine> From c76cad626cdd34aaa700336f1adbb28d36e90032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:47:37 +1000 Subject: [PATCH 726/774] Bump gson from 2.8.9 to 2.9.0 (#1769) Bumps [gson](https://github.com/google/gson) from 2.8.9 to 2.9.0. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.8.9...gson-parent-2.9.0) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 55ab982c26..882c1ef0fe 100644 --- a/pom.xml +++ b/pom.xml @@ -325,7 +325,7 @@ <!-- gson, to fetch entities from w3.org --> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>2.8.9</version> + <version>2.9.0</version> <scope>test</scope> </dependency> From 72612480cfc5a8b3c6ca31097cfd908121a638da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 13:47:56 +1000 Subject: [PATCH 727/774] Bump maven-bundle-plugin from 5.1.5 to 5.1.6 (#1768) Bumps maven-bundle-plugin from 5.1.5 to 5.1.6. --- updated-dependencies: - dependency-name: org.apache.felix:maven-bundle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 882c1ef0fe..22a9cecd55 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> - <version>5.1.5</version> + <version>5.1.6</version> <executions> <execution> <id>bundle-manifest</id> From eaf5028718840d71c9a7f94f538b65b135012702 Mon Sep 17 00:00:00 2001 From: Christian Schwier <trizzelnova+github@gmail.com> Date: Sun, 15 May 2022 05:55:40 +0200 Subject: [PATCH 728/774] Fix/safelist deep copy constructor (#1763) Copies nested data structures instead of using a shallow copy to avoid unexpected state mutations after copy constructor usage. --- src/main/java/org/jsoup/safety/Safelist.java | 26 +++++--- .../java/org/jsoup/safety/SafelistTest.java | 65 +++++++++++++++++++ 2 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/jsoup/safety/SafelistTest.java diff --git a/src/main/java/org/jsoup/safety/Safelist.java b/src/main/java/org/jsoup/safety/Safelist.java index 76d56d2be2..cc5604e5e3 100644 --- a/src/main/java/org/jsoup/safety/Safelist.java +++ b/src/main/java/org/jsoup/safety/Safelist.java @@ -63,10 +63,10 @@ XSS attack examples (that jsoup will safegaurd against the default Cleaner and S </p> */ public class Safelist { - private Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span] - private Map<TagName, Set<AttributeKey>> attributes; // tag -> attribute[]. allowed attributes [href] for a tag. - private Map<TagName, Map<AttributeKey, AttributeValue>> enforcedAttributes; // always set these attribute values - private Map<TagName, Map<AttributeKey, Set<Protocol>>> protocols; // allowed URL protocols for attributes + private final Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span] + private final Map<TagName, Set<AttributeKey>> attributes; // tag -> attribute[]. allowed attributes [href] for a tag. + private final Map<TagName, Map<AttributeKey, AttributeValue>> enforcedAttributes; // always set these attribute values + private final Map<TagName, Map<AttributeKey, Set<Protocol>>> protocols; // allowed URL protocols for attributes private boolean preserveRelativeLinks; // option to preserve relative links /** @@ -203,9 +203,19 @@ public Safelist() { public Safelist(Safelist copy) { this(); tagNames.addAll(copy.tagNames); - attributes.putAll(copy.attributes); - enforcedAttributes.putAll(copy.enforcedAttributes); - protocols.putAll(copy.protocols); + for (Map.Entry<TagName, Set<AttributeKey>> copyTagAttributes : copy.attributes.entrySet()) { + attributes.put(copyTagAttributes.getKey(), new HashSet<>(copyTagAttributes.getValue())); + } + for (Map.Entry<TagName, Map<AttributeKey, AttributeValue>> enforcedEntry : copy.enforcedAttributes.entrySet()) { + enforcedAttributes.put(enforcedEntry.getKey(), new HashMap<>(enforcedEntry.getValue())); + } + for (Map.Entry<TagName, Map<AttributeKey, Set<Protocol>>> protocolsEntry : copy.protocols.entrySet()) { + Map<AttributeKey, Set<Protocol>> attributeProtocolsCopy = new HashMap<>(); + for (Map.Entry<AttributeKey, Set<Protocol>> attributeProtocols : protocolsEntry.getValue().entrySet()) { + attributeProtocolsCopy.put(attributeProtocols.getKey(), new HashSet<>(attributeProtocols.getValue())); + } + protocols.put(protocolsEntry.getKey(), attributeProtocolsCopy); + } preserveRelativeLinks = copy.preserveRelativeLinks; } @@ -620,7 +630,7 @@ static Protocol valueOf(String value) { } abstract static class TypedValue { - private String value; + private final String value; TypedValue(String value) { Validate.notNull(value); diff --git a/src/test/java/org/jsoup/safety/SafelistTest.java b/src/test/java/org/jsoup/safety/SafelistTest.java new file mode 100644 index 0000000000..8b1c1ffd09 --- /dev/null +++ b/src/test/java/org/jsoup/safety/SafelistTest.java @@ -0,0 +1,65 @@ +package org.jsoup.safety; + +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; +import org.jsoup.parser.Tag; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class SafelistTest { + private static final String TEST_TAG = "testTag"; + private static final String TEST_ATTRIBUTE = "testAttribute"; + private static final String TEST_SCHEME = "valid-scheme"; + private static final String TEST_VALUE = TEST_SCHEME + "://testValue"; + + @Test + public void testCopyConstructor_noSideEffectOnTags() { + Safelist safelist1 = Safelist.none().addTags(TEST_TAG); + Safelist safelist2 = new Safelist(safelist1); + safelist1.addTags("invalidTag"); + + assertFalse(safelist2.isSafeTag("invalidTag")); + } + + @Test + public void testCopyConstructor_noSideEffectOnAttributes() { + Safelist safelist1 = Safelist.none().addAttributes(TEST_TAG, TEST_ATTRIBUTE); + Safelist safelist2 = new Safelist(safelist1); + safelist1.addAttributes(TEST_TAG, "invalidAttribute"); + + assertFalse(safelist2.isSafeAttribute(TEST_TAG, null, new Attribute("invalidAttribute", TEST_VALUE))); + } + + @Test + public void testCopyConstructor_noSideEffectOnEnforcedAttributes() { + Safelist safelist1 = Safelist.none().addEnforcedAttribute(TEST_TAG, TEST_ATTRIBUTE, TEST_VALUE); + Safelist safelist2 = new Safelist(safelist1); + safelist1.addEnforcedAttribute(TEST_TAG, TEST_ATTRIBUTE, "invalidValue"); + + for (Attribute enforcedAttribute : safelist2.getEnforcedAttributes(TEST_TAG)) { + assertNotEquals("invalidValue", enforcedAttribute.getValue()); + } + } + + @Test + public void testCopyConstructor_noSideEffectOnProtocols() { + final String invalidScheme = "invalid-scheme"; + Safelist safelist1 = Safelist.none() + .addAttributes(TEST_TAG, TEST_ATTRIBUTE) + .addProtocols(TEST_TAG, TEST_ATTRIBUTE, TEST_SCHEME); + Safelist safelist2 = new Safelist(safelist1); + safelist1.addProtocols(TEST_TAG, TEST_ATTRIBUTE, invalidScheme); + + Attributes attributes = new Attributes(); + Attribute invalidAttribute = new Attribute(TEST_ATTRIBUTE, invalidScheme + "://someValue"); + attributes.put(invalidAttribute); + Element invalidElement = new Element(Tag.valueOf(TEST_TAG), "", attributes); + + assertFalse(safelist2.isSafeAttribute(TEST_TAG, invalidElement, invalidAttribute)); + } + + +} From 2b4a1dacf69b0e0fde3baeebbcf7ce1228da0342 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 May 2022 13:59:08 +1000 Subject: [PATCH 729/774] Changelog for #1763 --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 50c1fa4563..387e81b40a 100644 --- a/CHANGES +++ b/CHANGES @@ -67,6 +67,10 @@ jsoup changelog <https://github.com/jhy/jsoup/issues/1688> <https://github.com/jhy/jsoup/issues/1689> + * Bugfix: when copy-creating a Safelist from another, perform a deep-copy of the original's settings, so that changes + to the original after creation do not affect the copy. + <https://github.com/jhy/jsoup/pull/1763> + * Bugfix [Fuzz]: speed improvement when parsing constructed HTML containing very deeply incorrectly stacked formatting elements with many attributes. <https://github.com/jhy/jsoup/issues/1695> From 3ffbeebc0959e2ada7ed1b4595338d2f09597e85 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 May 2022 15:34:01 +1000 Subject: [PATCH 730/774] [maven-release-plugin] prepare release jsoup-1.15.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 22a9cecd55..e3fc5d901d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.15.1-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> + <version>1.15.1</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.15.1</tag> </scm> <organization> <name>Jonathan Hedley</name> From 5fa6a4b46b0d06722e20e3527c373d4d6f6aaf5c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 May 2022 15:34:04 +1000 Subject: [PATCH 731/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e3fc5d901d..d43aa14f50 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.15.1</version><!-- remember to update previous version below for japicmp --> + <version>1.15.2-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>jsoup-1.15.1</tag> + <tag>HEAD</tag> </scm> <organization> <name>Jonathan Hedley</name> From 6c3c9f6824e378bbaa6adb7743072ab8d0ef04b4 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 May 2022 15:56:43 +1000 Subject: [PATCH 732/774] Release 1.15.1 Date --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 387e81b40a..2d1e175149 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.15.1 [PENDING] +*** Release 1.15.1 [2022-May-15] * Change: removed previously deprecated methods and classes (including org.jsoup.safety.Whitelist; use org.jsoup.safety.Safelist instead). @@ -18,7 +18,7 @@ jsoup changelog <https://github.com/jhy/jsoup/issues/1636> * Improvement: added the :matchesWholeText(regex) and :matchesWholeOwnText(regex) selectors, to match against whole - (non-normalized, case sensitive> element text and own text, respectively. + (non-normalized, case sensitive) element text and own text, respectively. <https://github.com/jhy/jsoup/issues/1636> * Improvement: when evaluating an XPath query against a context element, the complete document is now visible to the From b681a29b333cd7b2fa5fc261c869503ba2e2c9c1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 15 May 2022 16:28:58 +1000 Subject: [PATCH 733/774] Sync jetty versions --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d43aa14f50..9d5e4952e5 100644 --- a/pom.xml +++ b/pom.xml @@ -341,7 +341,7 @@ <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.44.v20210927</version> + <version>9.4.46.v20220331</version> <scope>test</scope> </dependency> From ccbd65f3bb10922b89f381c3141c11a10c68ee5c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 17 May 2022 20:30:06 +1000 Subject: [PATCH 734/774] In readToByteBuffer, read to max, not the internal buffer size Fixes #1774. Regressed by #1671. --- CHANGES | 6 ++++++ .../internal/ConstrainableInputStream.java | 9 +++------ .../org/jsoup/integration/ConnectTest.java | 20 +++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 2d1e175149..e49ef25ddb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,11 @@ jsoup changelog +*** Release 1.15.2 [PENDING] + * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not + already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default + internal buffer size is read. + <https://github.com/jhy/jsoup/issues/1774> + *** Release 1.15.1 [2022-May-15] * Change: removed previously deprecated methods and classes (including org.jsoup.safety.Whitelist; use org.jsoup.safety.Safelist instead). diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 0f7e3c3b88..5b6491363e 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -81,17 +81,14 @@ public ByteBuffer readToByteBuffer(int max) throws IOException { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize); int read; - int remaining = bufferSize; - while (true) { - read = read(readBuffer, 0, remaining); + read = read(readBuffer, 0, bufferSize); if (read == -1) break; if (localCapped) { // this local byteBuffer cap may be smaller than the overall maxSize (like when reading first bytes) - if (read >= remaining) { - outStream.write(readBuffer, 0, remaining); + if (read >= max) { + outStream.write(readBuffer, 0, max); break; } - remaining -= read; } outStream.write(readBuffer, 0, read); } diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 28e79dac60..42b4f950e1 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -671,4 +671,24 @@ public void maxBodySize() throws IOException { assertEquals("Large HTML", doc1.title()); assertEquals("Large HTML", doc2.title()); } + + @Test + public void maxBodySizeInReadToByteBuffer() throws IOException { + // https://github.com/jhy/jsoup/issues/1774 + // when calling readToByteBuffer, contents were not buffered up + String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K + + Connection.Response defaultRes = Jsoup.connect(url).execute(); + Connection.Response smallRes = Jsoup.connect(url).maxBodySize(50 * 1024).execute(); // crops + Connection.Response mediumRes = Jsoup.connect(url).maxBodySize(200 * 1024).execute(); // crops + Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop + Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute(); + + int actualDocText = 280735; + assertEquals(actualDocText, defaultRes.body().length()); + assertEquals(50 * 1024, smallRes.body().length()); + assertEquals(200 * 1024, mediumRes.body().length()); + assertEquals(actualDocText, largeRes.body().length()); + assertEquals(actualDocText, unlimitedRes.body().length()); + } } From f6d9aa050e3d58b6454d4eaef00f1d061f368eb4 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 18 May 2022 10:45:32 +1000 Subject: [PATCH 735/774] Don't skip blanks in preserveWhiteSpace descenders Fixes #1776. Regressed in f4d00c290325935186f75f1206d16397cc157ced --- CHANGES | 4 ++ src/main/java/org/jsoup/nodes/TextNode.java | 4 +- .../java/org/jsoup/nodes/ElementTest.java | 38 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e49ef25ddb..9c93e02754 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,10 @@ jsoup changelog internal buffer size is read. <https://github.com/jhy/jsoup/issues/1774> + * Bugfix: when serializing HTML, newlines in elements descending from a pre tag were incorrectly skipped. That caused + what should have been preformatted output to instead be a run of text. + <https://github.com/jhy/jsoup/issues/1776> + *** Release 1.15.1 [2022-May-15] * Change: removed previously deprecated methods and classes (including org.jsoup.safety.Whitelist; use org.jsoup.safety.Safelist instead). diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index a7e3cd51fe..c277da22b3 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -85,14 +85,14 @@ void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) thr final Element parent = parentNode instanceof Element ? ((Element) parentNode) : null; final boolean parentIndent = parent != null && parent.shouldIndent(out); final boolean blank = isBlank(); + final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); - if (parentIndent && StringUtil.startsWithNewline(coreValue()) && blank) // we are skippable whitespace + if (normaliseWhite && parentIndent && StringUtil.startsWithNewline(coreValue()) && blank) // we are skippable whitespace return; if (prettyPrint && ((siblingIndex == 0 && parent != null && parent.tag().formatAsBlock() && !blank) || (out.outline() && siblingNodes().size()>0 && !blank) )) indent(accum, depth, out); - final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); final boolean stripWhite = prettyPrint && parentNode instanceof Document; Entities.escape(accum, coreValue(), out, false, normaliseWhite, stripWhite); } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index f1a6556f71..6b06bf4e9b 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2216,4 +2216,42 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { assertEquals("Hello World", p.text()); assertEquals("Hello\nWorld", p.wholeText()); } + + @Test void preformatFlowsToChildTextNodes() { + // https://github.com/jhy/jsoup/issues/1776 + String html = "<div><pre>One\n<span>\nTwo</span>\n <span> \nThree</span>\n <span>Four <span>Five</span>\n Six\n</pre>"; + Document doc = Jsoup.parse(html); + doc.outputSettings().indentAmount(2).prettyPrint(true); + + Element div = doc.selectFirst("div"); + assertNotNull(div); + String actual = div.outerHtml(); + String expect = "<div>\n" + + " <pre>One\n" + + "<span>\n" + + "Two</span>\n" + + " <span> \n" + + "Three</span>\n" + + " <span>Four <span>Five</span>\n" + + " Six\n" + + "</span></pre>\n" + + "</div>"; + assertEquals(expect, actual); + + String expectText = "One\n" + + "\n" + + "Two\n" + + " \n" + + "Three\n" + + " Four Five\n" + + " Six\n"; + assertEquals(expectText, div.wholeText()); + + String expectOwn = "One\n" + + "\n" + + " \n" + + " "; + assertEquals(expectOwn, div.child(0).wholeOwnText()); + + } } \ No newline at end of file From 372ef90d8753690f23b1967aed42a106ce2b8ae2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 23 May 2022 16:58:22 +1000 Subject: [PATCH 736/774] Added {first|last}[Element]Child accessors Improvement: added Element.firstElementChild(), Element.lastElementChild(), Node.firstChild(), Node.lastChild(), as convenient accessors to those child nodes and elements. --- CHANGES | 3 ++ src/main/java/org/jsoup/nodes/Element.java | 35 +++++++++++++++++++++ src/main/java/org/jsoup/nodes/Node.java | 26 +++++++++++++++ src/test/java/org/jsoup/nodes/NodeTest.java | 33 +++++++++++++++++++ 4 files changed, 97 insertions(+) diff --git a/CHANGES b/CHANGES index 9c93e02754..5a1b807d63 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ jsoup changelog *** Release 1.15.2 [PENDING] + * Improvement: added Element.firstElementChild(), Element.lastElementChild(), Node.firstChild(), Node.lastChild(), + as convenient accessors to those child nodes and elements. + * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default internal buffer size is read. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 5d9279671d..57ea6d4c13 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -961,6 +961,41 @@ private static <E extends Element> int indexInList(Element search, List<E> eleme return 0; } + /** + Gets the first child of this Element that is an Element, or {@code null} if there is none. + @return the first Element child node, or null. + @see #firstChild() + @see #lastElementChild() + @since 1.15.2 + */ + public @Nullable Element firstElementChild() { + if (childNodeSize() == 0) return null; + List<Node> children = ensureChildNodes(); + int size = children.size(); + for (int i = 0; i < size; i++) { + Node node = children.get(i); + if (node instanceof Element) return (Element) node; + } + return null; + } + + /** + Gets the last child of this Element that is an Element, or @{code null} if there is none. + @return the last Element child node, or null. + @see #lastChild() + @see #firstElementChild() + @since 1.15.2 + */ + public @Nullable Element lastElementChild() { + if (childNodeSize() == 0) return null; + List<Node> children = ensureChildNodes(); + for (int i = children.size() -1; i >= 0; i--) { + Node node = children.get(i); + if (node instanceof Element) return (Element) node; + } + return null; + } + // DOM type methods /** diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 993139579f..e631a52598 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -616,6 +616,32 @@ protected void setSiblingIndex(int siblingIndex) { this.siblingIndex = siblingIndex; } + /** + Gets the first child node of this node, or {@code null} if there is none. This could be any Node type, such as an + Element, TextNode, Comment, etc. Use {@link Element#firstElementChild()} to get the first Element child. + @return the first child node, or null if there are no children. + @see Element#firstElementChild() + @see #lastChild() + @since 1.15.2 + */ + public @Nullable Node firstChild() { + if (childNodeSize() == 0) return null; + return ensureChildNodes().get(0); + } + + /** + Gets the last child node of this node, or {@code null} if there is none. + @return the last child node, or null if there are no children. + @see Element#lastElementChild() + @see #firstChild() + @since 1.15.2 + */ + public @Nullable Node lastChild() { + if (childNodeSize() == 0) return null; + List<Node> children = ensureChildNodes(); + return children.get(children.size() - 1); + } + /** * Perform a depth-first traversal through this node and its descendants. * @param nodeVisitor the visitor callbacks to perform on each node diff --git a/src/test/java/org/jsoup/nodes/NodeTest.java b/src/test/java/org/jsoup/nodes/NodeTest.java index af9db4d9a1..37bc702772 100644 --- a/src/test/java/org/jsoup/nodes/NodeTest.java +++ b/src/test/java/org/jsoup/nodes/NodeTest.java @@ -369,4 +369,37 @@ private Attributes singletonAttributes() { assertEquals(1, docClone.childNodes().size()); // check did not get the second div as the owner's children assertEquals(textClone, docClone.childNode(0)); // note not the head or the body -- not normalized } + + @Test + void firstAndLastChild() { + String html = "<div>One <span>Two</span> <a href></a> Three</div>"; + Document doc = Jsoup.parse(html); + Element div = doc.selectFirst("div"); + Element a = doc.selectFirst("a"); + assertNotNull(div); + assertNotNull(a); + + // nodes + TextNode first = (TextNode) div.firstChild(); + assertEquals("One ", first.text()); + + TextNode last = (TextNode) div.lastChild(); + assertEquals(" Three", last.text()); + + assertNull(a.firstChild()); + assertNull(a.lastChild()); + + // elements + Element firstEl = div.firstElementChild(); + assertEquals("span", firstEl.tagName()); + + Element lastEl = div.lastElementChild(); + assertEquals("a", lastEl.tagName()); + + assertNull(a.firstElementChild()); + assertNull(a.lastElementChild()); + + assertNull(firstEl.firstElementChild()); + assertNull(firstEl.lastElementChild()); + } } From ef0065dcf1a523b57bebf28cf4f0395cd4970082 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 23 May 2022 17:15:23 +1000 Subject: [PATCH 737/774] Tidy up child node accessor --- src/main/java/org/jsoup/nodes/Node.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index e631a52598..a3dfae2d3a 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -431,8 +431,7 @@ public Node wrap(String html) { */ public @Nullable Node unwrap() { Validate.notNull(parentNode); - final List<Node> childNodes = ensureChildNodes(); - Node firstChild = childNodes.size() > 0 ? childNodes.get(0) : null; + Node firstChild = firstChild(); parentNode.addChildren(siblingIndex, this.childNodesAsArray()); this.remove(); @@ -549,14 +548,14 @@ protected void reparentChild(Node child) { private void reindexChildren(int start) { if (childNodeSize() == 0) return; final List<Node> childNodes = ensureChildNodes(); - - for (int i = start; i < childNodes.size(); i++) { + final int size = childNodes.size(); + for (int i = start; i < size; i++) { childNodes.get(i).setSiblingIndex(i); } } /** - Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not + Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not include this node (a node is not a sibling of itself). @return node siblings. If the node has no parent, returns an empty list. */ From bb7f93b4c8f7cb47029fe542334b09799d7b8a95 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 23 May 2022 17:25:35 +1000 Subject: [PATCH 738/774] Tweaked for fewer child.size() hits --- src/main/java/org/jsoup/nodes/Element.java | 9 +++++---- src/main/java/org/jsoup/nodes/Node.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 57ea6d4c13..a934a2f8d7 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -969,9 +969,9 @@ private static <E extends Element> int indexInList(Element search, List<E> eleme @since 1.15.2 */ public @Nullable Element firstElementChild() { - if (childNodeSize() == 0) return null; + final int size = childNodeSize(); + if (size == 0) return null; List<Node> children = ensureChildNodes(); - int size = children.size(); for (int i = 0; i < size; i++) { Node node = children.get(i); if (node instanceof Element) return (Element) node; @@ -987,9 +987,10 @@ private static <E extends Element> int indexInList(Element search, List<E> eleme @since 1.15.2 */ public @Nullable Element lastElementChild() { - if (childNodeSize() == 0) return null; + final int size = childNodeSize(); + if (size == 0) return null; List<Node> children = ensureChildNodes(); - for (int i = children.size() -1; i >= 0; i--) { + for (int i = size -1; i >= 0; i--) { Node node = children.get(i); if (node instanceof Element) return (Element) node; } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index a3dfae2d3a..9c9e201dc7 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -546,9 +546,9 @@ protected void reparentChild(Node child) { } private void reindexChildren(int start) { - if (childNodeSize() == 0) return; + final int size = childNodeSize(); + if (size == 0) return; final List<Node> childNodes = ensureChildNodes(); - final int size = childNodes.size(); for (int i = start; i < size; i++) { childNodes.get(i).setSiblingIndex(i); } @@ -636,9 +636,10 @@ protected void setSiblingIndex(int siblingIndex) { @since 1.15.2 */ public @Nullable Node lastChild() { - if (childNodeSize() == 0) return null; + final int size = childNodeSize(); + if (size == 0) return null; List<Node> children = ensureChildNodes(); - return children.get(children.size() - 1); + return children.get(size - 1); } /** From 103d7864a6d54391a2a98c5027e03d5a777ebc71 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 23 May 2022 17:32:46 +1000 Subject: [PATCH 739/774] Removed external URL test dependency Would cause test failures if no network was available during test execution. Since it was authored, the buffer variants that tripped the original bug have had better coverage added, no it is no longer required. Fixes #1733 Closes #1754 --- src/test/java/org/jsoup/integration/ConnectTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 42b4f950e1..429e925821 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -517,15 +517,12 @@ public void canFetchBinaryAsBytes() throws IOException { @Test public void handlesUnknownEscapesAcrossBuffer() throws IOException { String localPath = "/htmltests/escapes-across-buffer.html"; - String url = - "https://gist.githubusercontent.com/krystiangorecki/d3bad50ef5615f06b077438607423533/raw/71adfdf81121282ea936510ed6cfe440adeb2d83/JsoupIssue1218.html"; String localUrl = FileServlet.urlTo(localPath); - Document docFromGithub = Jsoup.connect(url).get(); // different chunks meant GH would error but local not... Document docFromLocalServer = Jsoup.connect(localUrl).get(); Document docFromFileRead = Jsoup.parse(ParseTest.getFile(localPath), "UTF-8"); - String text = docFromGithub.body().text(); + String text = docFromLocalServer.body().text(); assertEquals(14766, text.length()); assertEquals(text, docFromLocalServer.body().text()); assertEquals(text, docFromFileRead.body().text()); From b0ac5e06a75cfe00ab010291c9df03cbbf8d655d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 24 May 2022 13:58:19 +1000 Subject: [PATCH 740/774] Track API compat against last release (1.15.1) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 9d5e4952e5..dd5cbfe2d1 100644 --- a/pom.xml +++ b/pom.xml @@ -199,7 +199,7 @@ <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.14.3</version> + <version>1.15.1</version> <type>jar</type> </dependency> </oldVersion> @@ -208,9 +208,9 @@ <onlyModified>false</onlyModified> <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications> <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications> - <excludes> + <!-- <excludes> <exclude>@java.lang.Deprecated</exclude> - </excludes> + </excludes> --> <overrideCompatibilityChangeParameters> <!-- allows new default and move to default methods. compatible as long as existing binaries aren't making calls via reflection. if so, they need to catch errors anyway. --> <overrideCompatibilityChangeParameter> From 5abf30a8ec16ec447123c0f7c4e5763cd7e1797b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 13 Jun 2022 17:37:45 +1000 Subject: [PATCH 741/774] Improvement: adds optional source position tracking (#1790) * Improvement: adds optional source position tracking Updated the Tokeniser to optionally track the input source character ranges for nodes, and the end tags for Elements. This gives the ability to (for example) visually link an editor to the parsed DOM. --- CHANGES | 4 + src/main/java/org/jsoup/helper/Validate.java | 12 ++ src/main/java/org/jsoup/nodes/Attributes.java | 57 +++++- src/main/java/org/jsoup/nodes/Element.java | 13 ++ src/main/java/org/jsoup/nodes/Node.java | 12 ++ src/main/java/org/jsoup/nodes/Range.java | 187 ++++++++++++++++++ .../org/jsoup/parser/CharacterReader.java | 49 +++-- .../org/jsoup/parser/HtmlTreeBuilder.java | 24 ++- .../jsoup/parser/HtmlTreeBuilderState.java | 1 + .../java/org/jsoup/parser/ParseError.java | 2 +- .../java/org/jsoup/parser/ParseSettings.java | 6 +- src/main/java/org/jsoup/parser/Parser.java | 33 +++- src/main/java/org/jsoup/parser/Token.java | 31 ++- src/main/java/org/jsoup/parser/Tokeniser.java | 42 ++-- .../java/org/jsoup/parser/TreeBuilder.java | 40 +++- .../java/org/jsoup/parser/XmlTreeBuilder.java | 18 +- .../java/org/jsoup/parser/package-info.java | 3 + .../org/jsoup/integration/ConnectTest.java | 2 +- .../java/org/jsoup/nodes/PositionTest.java | 180 +++++++++++++++++ src/test/resources/htmltests/large.html | 2 +- 20 files changed, 655 insertions(+), 63 deletions(-) create mode 100644 src/main/java/org/jsoup/nodes/Range.java create mode 100644 src/test/java/org/jsoup/nodes/PositionTest.java diff --git a/CHANGES b/CHANGES index 5a1b807d63..6014b78b8f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog *** Release 1.15.2 [PENDING] + * Improvement: added the ability to track the position (line, column, index) in the original input source from where + a given node was parsed. Accessible via Node.sourceRange() and Element.endSourceRange(). + <https://github.com/jhy/jsoup/pull/1790> + * Improvement: added Element.firstElementChild(), Element.lastElementChild(), Node.firstChild(), Node.lastChild(), as convenient accessors to those child nodes and elements. diff --git a/src/main/java/org/jsoup/helper/Validate.java b/src/main/java/org/jsoup/helper/Validate.java index e934faa944..97e7b67cfe 100644 --- a/src/main/java/org/jsoup/helper/Validate.java +++ b/src/main/java/org/jsoup/helper/Validate.java @@ -28,6 +28,18 @@ public static void notNull(@Nullable Object obj, String msg) { throw new IllegalArgumentException(msg); } + /** + Verifies the input object is not null, and returns that object. Effectively this casts a nullable object to a non- + null object. (Works around lack of Objects.requestNonNull in Android version.) + * @param obj nullable object to case to not-null + * @return the object, or throws an NPE. + */ + public static Object ensureNotNull(@Nullable Object obj) { + if (obj == null) + throw new NullPointerException(); + else return obj; + } + /** * Validates that the value is true * @param val object to test diff --git a/src/main/java/org/jsoup/nodes/Attributes.java b/src/main/java/org/jsoup/nodes/Attributes.java index 2e989fd385..76b6590e35 100644 --- a/src/main/java/org/jsoup/nodes/Attributes.java +++ b/src/main/java/org/jsoup/nodes/Attributes.java @@ -49,7 +49,7 @@ public class Attributes implements Iterable<Attribute>, Cloneable { // the number of instance fields is kept as low as possible giving an object size of 24 bytes private int size = 0; // number of slots used (not total capacity, which is keys.length) String[] keys = new String[InitialCapacity]; - String[] vals = new String[InitialCapacity]; + Object[] vals = new Object[InitialCapacity]; // Genericish: all non-internal attribute values must be Strings and are cast on access. // check there's room for more private void checkCapacity(int minNewSize) { @@ -84,8 +84,9 @@ private int indexOfKeyIgnoreCase(String key) { } // we track boolean attributes as null in values - they're just keys. so returns empty for consumers - static String checkNotNull(@Nullable String val) { - return val == null ? EmptyString : val; + // casts to String, so only for non-internal attributes + static String checkNotNull(@Nullable Object val) { + return val == null ? EmptyString : (String) val; } /** @@ -109,16 +110,33 @@ public String getIgnoreCase(String key) { return i == NotFound ? EmptyString : checkNotNull(vals[i]); } + /** + Get an arbitrary user data object by key. + * @param key case sensitive key to the object. + * @return the object associated to this key, or {@code null} if not found. + */ + @Nullable + Object getUserData(String key) { + Validate.notNull(key); + if (!isInternalKey(key)) key = internalKey(key); + int i = indexOfKeyIgnoreCase(key); + return i == NotFound ? null : vals[i]; + } + /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ public Attributes add(String key, @Nullable String value) { + addObject(key, value); + return this; + } + + private void addObject(String key, @Nullable Object value) { checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; - return this; } /** @@ -137,6 +155,25 @@ public Attributes put(String key, @Nullable String value) { return this; } + /** + Put an arbitrary user-data object by key. Will be treated as an internal attribute, so will not be emitted in HTML. + * @param key case sensitive key + * @param value object value + * @return these attributes + * @see #getUserData(String) + */ + Attributes putUserData(String key, Object value) { + Validate.notNull(key); + if (!isInternalKey(key)) key = internalKey(key); + Validate.notNull(value); + int i = indexOfKey(key); + if (i != NotFound) + vals[i] = value; + else + addObject(key, value); + return this; + } + void putIgnoreCase(String key, @Nullable String value) { int i = indexOfKeyIgnoreCase(key); if (i != NotFound) { @@ -299,7 +336,7 @@ public boolean hasNext() { @Override public Attribute next() { - final Attribute attr = new Attribute(keys[i], vals[i], Attributes.this); + final Attribute attr = new Attribute(keys[i], (String) vals[i], Attributes.this); i++; return attr; } @@ -313,14 +350,14 @@ public void remove() { /** Get the attributes as a List, for iteration. - @return an view of the attributes as an unmodifiable List. + @return a view of the attributes as an unmodifiable List. */ public List<Attribute> asList() { ArrayList<Attribute> list = new ArrayList<>(size); for (int i = 0; i < size; i++) { if (isInternalKey(keys[i])) continue; // skip internal keys - Attribute attr = new Attribute(keys[i], vals[i], Attributes.this); + Attribute attr = new Attribute(keys[i], (String) vals[i], Attributes.this); list.add(attr); } return Collections.unmodifiableList(list); @@ -356,7 +393,7 @@ final void html(final Appendable accum, final Document.OutputSettings out) throw continue; final String key = Attribute.getValidKey(keys[i], out.syntax()); if (key != null) - Attribute.htmlNoValidate(key, vals[i], accum.append(' '), out); + Attribute.htmlNoValidate(key, (String) vals[i], accum.append(' '), out); } } @@ -383,8 +420,8 @@ public boolean equals(@Nullable Object o) { int thatI = that.indexOfKey(key); if (thatI == NotFound) return false; - String val = vals[i]; - String thatVal = that.vals[thatI]; + Object val = vals[i]; + Object thatVal = that.vals[thatI]; if (val == null) { if (thatVal != null) return false; diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index a934a2f8d7..3b2d54aec4 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1653,6 +1653,19 @@ public Element val(String value) { return this; } + /** + Get the source range (start and end positions) of the end (closing) tag for this Element. Position tracking must be + enabled prior to parsing the content. + @return the range of the closing tag for this element, if it was explicitly closed in the source. {@code Untracked} + otherwise. + @see org.jsoup.parser.Parser#setTrackPosition(boolean) + @see Node#sourceRange() + @since 1.15.2 + */ + public Range endSourceRange() { + return Range.of(this, false); + } + boolean shouldIndent(final Document.OutputSettings out) { return out.prettyPrint() && isFormatAsBlock(out) && !isInlineable(out); } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 9c9e201dc7..fdbc65eefb 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -713,6 +713,18 @@ public <T extends Appendable> T html(T appendable) { return appendable; } + /** + Get the source range (start and end positions) in the original input source that this node was parsed from. Position + tracking must be enabled prior to parsing the content. For an Element, this will be the positions of the start tag. + @return the range for the start of the node. + @see org.jsoup.parser.Parser#setTrackPosition(boolean) + @see Element#endSourceRange() + @since 1.15.2 + */ + public Range sourceRange() { + return Range.of(this, true); + } + /** * Gets this node's outer HTML. * @return outer HTML. diff --git a/src/main/java/org/jsoup/nodes/Range.java b/src/main/java/org/jsoup/nodes/Range.java new file mode 100644 index 0000000000..d110d4c8d2 --- /dev/null +++ b/src/main/java/org/jsoup/nodes/Range.java @@ -0,0 +1,187 @@ +package org.jsoup.nodes; + +import org.jsoup.helper.Validate; + +/** + A Range object tracks the character positions in the original input source where a Node starts or ends. If you want to + track these positions, tracking must be enabled in the Parser with + {@link org.jsoup.parser.Parser#setTrackPosition(boolean)}. + @see Node#sourceRange() + @since 1.15.2 + */ +public class Range { + private final Position start, end; + + private static final String RangeKey = Attributes.internalKey("jsoup.sourceRange"); + private static final String EndRangeKey = Attributes.internalKey("jsoup.endSourceRange"); + private static final Position UntrackedPos = new Position(-1, -1, -1); + private static final Range Untracked = new Range(UntrackedPos, UntrackedPos); + + /** + Creates a new Range with start and end Positions. Called by TreeBuilder when position tracking is on. + * @param start the start position + * @param end the end position + */ + public Range(Position start, Position end) { + this.start = start; + this.end = end; + } + + /** + Get the start position of this node. + * @return the start position + */ + public Position start() { + return start; + } + + /** + Get the end position of this node. + * @return the end position + */ + public Position end() { + return end; + } + + /** + Test if this source range was tracked during parsing. + * @return true if this was tracked during parsing, false otherwise (and all fields will be {@code -1}). + */ + public boolean isTracked() { + return this != Untracked; + } + + /** + Retrieves the source range for a given Node. + * @param node the node to retrieve the position for + * @param start if this is the starting range. {@code false} for Element end tags. + * @return the Range, or the Untracked (-1) position if tracking is disabled. + */ + static Range of(Node node, boolean start) { + final String key = start ? RangeKey : EndRangeKey; + if (!node.hasAttr(key)) + return Untracked; + else + return (Range) Validate.ensureNotNull(node.attributes().getUserData(key)); + } + + /** + Internal jsoup method, called by the TreeBuilder. Tracks a Range for a Node. + * @param node the node to associate this position to + * @param start if this is the starting range. {@code false} for Element end tags. + */ + public void track(Node node, boolean start) { + node.attributes().putUserData(start ? RangeKey : EndRangeKey, this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Range range = (Range) o; + + if (!start.equals(range.start)) return false; + return end.equals(range.end); + } + + @Override + public int hashCode() { + int result = start.hashCode(); + result = 31 * result + end.hashCode(); + return result; + } + + /** + Gets a String presentation of this Range, in the format {@code line,column:pos-line,column:pos}. + * @return a String + */ + @Override + public String toString() { + return start + "-" + end; + } + + /** + A Position object tracks the character position in the original input source where a Node starts or ends. If you want to + track these positions, tracking must be enabled in the Parser with + {@link org.jsoup.parser.Parser#setTrackPosition(boolean)}. + @see Node#sourceRange() + */ + public static class Position { + private final int pos, lineNumber, columnNumber; + + /** + Create a new Position object. Called by the TreeBuilder if source position tracking is on. + * @param pos position index + * @param lineNumber line number + * @param columnNumber column number + */ + public Position(int pos, int lineNumber, int columnNumber) { + this.pos = pos; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + /** + Gets the position index (0-based) of the original input source that this Position was read at. This tracks the + total number of characters read into the source at this position, regardless of the number of preceeding lines. + * @return the position, or {@code -1} if untracked. + */ + public int pos() { + return pos; + } + + /** + Gets the line number (1-based) of the original input source that this Position was read at. + * @return the line number, or {@code -1} if untracked. + */ + public int lineNumber() { + return lineNumber; + } + + /** + Gets the cursor number (1-based) of the original input source that this Position was read at. The cursor number + resets to 1 on every new line. + * @return the cursor number, or {@code -1} if untracked. + */ + public int columnNumber() { + return columnNumber; + } + + /** + Test if this position was tracked during parsing. + * @return true if this was tracked during parsing, false otherwise (and all fields will be {@code -1}). + */ + public boolean isTracked() { + return this != UntrackedPos; + } + + /** + Gets a String presentation of this Position, in the format {@code line,column:pos}. + * @return a String + */ + @Override + public String toString() { + return lineNumber + "," + columnNumber + ":" + pos; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Position position = (Position) o; + if (pos != position.pos) return false; + if (lineNumber != position.lineNumber) return false; + return columnNumber == position.columnNumber; + } + + @Override + public int hashCode() { + int result = pos; + result = 31 * result + lineNumber; + result = 31 * result + columnNumber; + return result; + } + + } +} diff --git a/src/main/java/org/jsoup/parser/CharacterReader.java b/src/main/java/org/jsoup/parser/CharacterReader.java index 605b19978e..df902b1684 100644 --- a/src/main/java/org/jsoup/parser/CharacterReader.java +++ b/src/main/java/org/jsoup/parser/CharacterReader.java @@ -109,7 +109,7 @@ private void bufferUp() { } /** - * Gets the current cursor position in the content. + * Gets the position currently read to in the content. Starts at 0. * @return current position */ public int pos() { @@ -149,14 +149,18 @@ Get the current line number (that the reader has consumed to). Starts at line #1 @see #trackNewlines(boolean) */ public int lineNumber() { + return lineNumber(pos()); + } + + int lineNumber(int pos) { + // note that this impl needs to be called before the next buffer up or line numberoffset will be wrong. if that + // causes issues, can remove the reset of newlinepositions during buffer, at the cost of a larger tracking array if (!isTrackNewlines()) return 1; - int i = lineNumIndex(); + int i = lineNumIndex(pos); if (i == -1) return lineNumberOffset; // first line - if (i < 0) - return Math.abs(i) + lineNumberOffset - 1; return i + lineNumberOffset + 1; } @@ -166,16 +170,18 @@ Get the current column number (that the reader has consumed to). Starts at colum @since 1.14.3 @see #trackNewlines(boolean) */ - int columnNumber() { + public int columnNumber() { + return columnNumber(pos()); + } + + int columnNumber(int pos) { if (!isTrackNewlines()) - return pos() + 1; + return pos + 1; - int i = lineNumIndex(); + int i = lineNumIndex(pos); if (i == -1) - return pos() + 1; - if (i < 0) - i = Math.abs(i) - 2; - return pos() - newlinePositions.get(i) + 1; + return pos + 1; + return pos - newlinePositions.get(i) + 1; } /** @@ -189,9 +195,11 @@ String cursorPos() { return lineNumber() + ":" + columnNumber(); } - private int lineNumIndex() { + private int lineNumIndex(int pos) { if (!isTrackNewlines()) return 0; - return Collections.binarySearch(newlinePositions, pos()); + int i = Collections.binarySearch(newlinePositions, pos); + if (i < -1) i = Math.abs(i) - 2; + return i; } /** @@ -201,13 +209,16 @@ private void scanBufferForNewlines() { if (!isTrackNewlines()) return; - lineNumberOffset += newlinePositions.size(); - int lastPos = newlinePositions.size() > 0 ? newlinePositions.get(newlinePositions.size() -1) : -1; - newlinePositions.clear(); - if (lastPos != -1) { - newlinePositions.add(lastPos); // roll the last pos to first, for cursor num after buffer - lineNumberOffset--; // as this takes a position + if (newlinePositions.size() > 0) { + // work out the line number that we have read up to (as we have likely scanned past this point) + int index = lineNumIndex(readerPos); + if (index == -1) index = 0; // first line + int linePos = newlinePositions.get(index); + lineNumberOffset += index; // the num lines we've read up to + newlinePositions.clear(); + newlinePositions.add(linePos); // roll the last read pos to first, for cursor num after buffer } + for (int i = bufPos; i < bufLength; i++) { if (charBuf[i] == '\n') newlinePositions.add(1 + readerPos + i); diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 7ddda8e7d9..58760d1679 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -246,7 +246,7 @@ Element insert(final Token.StartTag startTag) { } Element el = new Element(tagFor(startTag.name(), settings), null, settings.normalizeAttributes(startTag.attributes)); - insert(el); + insert(el, startTag); return el; } @@ -257,14 +257,19 @@ Element insertStartTag(String startTagName) { } void insert(Element el) { - insertNode(el); + insertNode(el, null); + stack.add(el); + } + + private void insert(Element el, @Nullable Token token) { + insertNode(el, token); stack.add(el); } Element insertEmpty(Token.StartTag startTag) { Tag tag = tagFor(startTag.name(), settings); Element el = new Element(tag, null, settings.normalizeAttributes(startTag.attributes)); - insertNode(el); + insertNode(el, startTag); if (startTag.isSelfClosing()) { if (tag.isKnownTag()) { if (!tag.isEmpty()) @@ -285,7 +290,7 @@ FormElement insertForm(Token.StartTag startTag, boolean onStack, boolean checkTe } else setFormElement(el); - insertNode(el); + insertNode(el, startTag); if (onStack) stack.add(el); return el; @@ -293,7 +298,7 @@ FormElement insertForm(Token.StartTag startTag, boolean onStack, boolean checkTe void insert(Token.Comment commentToken) { Comment comment = new Comment(commentToken.getData()); - insertNode(comment); + insertNode(comment, commentToken); } void insert(Token.Character characterToken) { @@ -309,9 +314,10 @@ else if (isContentForTagData(tagName)) else node = new TextNode(data); el.appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack. + onNodeInserted(node, characterToken); } - private void insertNode(Node node) { + private void insertNode(Node node, @Nullable Token token) { // if the stack hasn't been set up yet, elements (doctype, comments) go into the doc if (stack.isEmpty()) doc.appendChild(node); @@ -325,6 +331,7 @@ else if (isFosterInserts() && StringUtil.inSorted(currentElement().normalName(), if (formElement != null) formElement.addElement((Element) node); } + onNodeInserted(node, token); } Element pop() { @@ -390,8 +397,11 @@ Element popStackToClose(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element el = stack.get(pos); stack.remove(pos); - if (el.normalName().equals(elName)) + if (el.normalName().equals(elName)) { + if (currentToken instanceof Token.EndTag) + onNodeClosed(el, currentToken); return el; + } } return null; } diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index af1fefe453..57e42d4dfc 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -31,6 +31,7 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); doctype.setPubSysKey(d.getPubSysKey()); tb.getDocument().appendChild(doctype); + tb.onNodeInserted(doctype, t); if (d.isForceQuirks()) tb.getDocument().quirksMode(Document.QuirksMode.quirks); tb.transition(BeforeHtml); diff --git a/src/main/java/org/jsoup/parser/ParseError.java b/src/main/java/org/jsoup/parser/ParseError.java index feccea6800..7571aa4902 100644 --- a/src/main/java/org/jsoup/parser/ParseError.java +++ b/src/main/java/org/jsoup/parser/ParseError.java @@ -49,7 +49,7 @@ public int getPosition() { } /** - Get the formatted line:column cursor position where the error occured. + Get the formatted line:column cursor position where the error occurred. @return line:number cursor position */ public String getCursorPos() { diff --git a/src/main/java/org/jsoup/parser/ParseSettings.java b/src/main/java/org/jsoup/parser/ParseSettings.java index b83ac07764..56ff672d05 100644 --- a/src/main/java/org/jsoup/parser/ParseSettings.java +++ b/src/main/java/org/jsoup/parser/ParseSettings.java @@ -1,11 +1,11 @@ package org.jsoup.parser; import org.jsoup.nodes.Attributes; - +import javax.annotation.Nullable; import static org.jsoup.internal.Normalizer.lowerCase; /** - * Controls parser settings, to optionally preserve tag and/or attribute name case. + * Controls parser case settings, to optionally preserve tag and/or attribute name case. */ public class ParseSettings { /** @@ -73,7 +73,7 @@ public String normalizeAttribute(String name) { return name; } - Attributes normalizeAttributes(Attributes attributes) { + @Nullable Attributes normalizeAttributes(@Nullable Attributes attributes) { if (attributes != null && !preserveAttributeCase) { attributes.normalize(); } diff --git a/src/main/java/org/jsoup/parser/Parser.java b/src/main/java/org/jsoup/parser/Parser.java index efc5f3c449..93bb1c031e 100644 --- a/src/main/java/org/jsoup/parser/Parser.java +++ b/src/main/java/org/jsoup/parser/Parser.java @@ -16,6 +16,7 @@ public class Parser { private TreeBuilder treeBuilder; private ParseErrorList errors; private ParseSettings settings; + private boolean trackPosition = false; /** * Create a new Parser, using the specified TreeBuilder @@ -39,6 +40,7 @@ private Parser(Parser copy) { treeBuilder = copy.treeBuilder.newInstance(); // because extended errors = new ParseErrorList(copy.errors); // only copies size, not contents settings = new ParseSettings(copy.settings); + trackPosition = copy.trackPosition; } public Document parseInput(String html, String baseUri) { @@ -63,7 +65,7 @@ public TreeBuilder getTreeBuilder() { /** * Update the TreeBuilder used when parsing content. - * @param treeBuilder current TreeBuilder + * @param treeBuilder new TreeBuilder * @return this, for chaining */ public Parser setTreeBuilder(TreeBuilder treeBuilder) { @@ -99,11 +101,40 @@ public ParseErrorList getErrors() { return errors; } + /** + Test if position tracking is enabled. If it is, Nodes will have a Position to track where in the original input + source they were created from. By default, tracking is not enabled. + * @return current track position setting + */ + public boolean isTrackPosition() { + return trackPosition; + } + + /** + Enable or disable source position tracking. If enabled, Nodes will have a Position to track where in the original + input source they were created from. + @param trackPosition position tracking setting; {@code true} to enable + @return this Parser, for chaining + */ + public Parser setTrackPosition(boolean trackPosition) { + this.trackPosition = trackPosition; + return this; + } + + /** + Update the ParseSettings of this Parser, to control the case sensitivity of tags and attributes. + * @param settings the new settings + * @return this Parser + */ public Parser settings(ParseSettings settings) { this.settings = settings; return this; } + /** + Gets the current ParseSettings for this Parser + * @return current ParseSettings + */ public ParseSettings settings() { return settings; } diff --git a/src/main/java/org/jsoup/parser/Token.java b/src/main/java/org/jsoup/parser/Token.java index 7f4296584e..819b8aef39 100644 --- a/src/main/java/org/jsoup/parser/Token.java +++ b/src/main/java/org/jsoup/parser/Token.java @@ -5,13 +5,13 @@ import javax.annotation.Nullable; -import static org.jsoup.internal.Normalizer.lowerCase; - /** * Parse tokens for the Tokeniser. */ abstract class Token { TokenType type; + static final int Unset = -1; + private int startPos, endPos = Unset; // position in CharacterReader this token was read from private Token() { } @@ -24,7 +24,27 @@ String tokenType() { * Reset the data represent by this token, for reuse. Prevents the need to create transfer objects for every * piece of data, which immediately get GCed. */ - abstract Token reset(); + Token reset() { + startPos = Unset; + endPos = Unset; + return this; + } + + int startPos() { + return startPos; + } + + void startPos(int pos) { + startPos = pos; + } + + int endPos() { + return endPos; + } + + void endPos(int pos) { + endPos = pos; + } static void reset(StringBuilder sb) { if (sb != null) { @@ -45,6 +65,7 @@ static final class Doctype extends Token { @Override Token reset() { + super.reset(); reset(name); pubSysKey = null; reset(publicIdentifier); @@ -97,6 +118,7 @@ static abstract class Tag extends Token { @Override Tag reset() { + super.reset(); tagName = null; normalName = null; reset(attrName); @@ -315,6 +337,7 @@ final static class Comment extends Token { @Override Token reset() { + super.reset(); reset(data); dataS = null; bogus = false; @@ -369,6 +392,7 @@ static class Character extends Token { @Override Token reset() { + super.reset(); data = null; return this; } @@ -408,6 +432,7 @@ final static class EOF extends Token { @Override Token reset() { + super.reset(); return this; } diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 1ebf0871d9..0eb40875ee 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -49,6 +49,9 @@ final class Tokeniser { private String lastStartTag; // the last start tag emitted, to test appropriate end tag @Nullable private String lastStartCloseSeq; // "</" + lastStartTag, so we can quickly check for that in RCData + private static final int Unset = -1; + private int markupStartPos, charStartPos = Unset; // reader pos at the start of markup / characters. updated on state transition + Tokeniser(CharacterReader reader, ParseErrorList errors) { this.reader = reader; this.errors = errors; @@ -64,8 +67,9 @@ Token read() { if (cb.length() != 0) { String str = cb.toString(); cb.delete(0, cb.length()); + Token token = charPending.data(str); charsString = null; - return charPending.data(str); + return token; } else if (charsString != null) { Token token = charPending.data(charsString); charsString = null; @@ -81,6 +85,9 @@ void emit(Token token) { emitPending = token; isEmitPending = true; + token.startPos(markupStartPos); + token.endPos(reader.pos()); + charStartPos = Unset; if (token.type == Token.TokenType.StartTag) { Token.StartTag startTag = (Token.StartTag) token; @@ -98,38 +105,41 @@ void emit(final String str) { // does not set isEmitPending; read checks that if (charsString == null) { charsString = str; - } - else { + } else { if (charsBuilder.length() == 0) { // switching to string builder as more than one emit before read charsBuilder.append(charsString); } charsBuilder.append(str); } + charPending.startPos(charStartPos); + charPending.endPos(reader.pos()); } // variations to limit need to create temp strings void emit(final StringBuilder str) { if (charsString == null) { charsString = str.toString(); - } - else { + } else { if (charsBuilder.length() == 0) { charsBuilder.append(charsString); } charsBuilder.append(str); } + charPending.startPos(charStartPos); + charPending.endPos(reader.pos()); } void emit(char c) { if (charsString == null) { charsString = String.valueOf(c); - } - else { + } else { if (charsBuilder.length() == 0) { charsBuilder.append(charsString); } charsBuilder.append(c); } + charPending.startPos(charStartPos); + charPending.endPos(reader.pos()); } void emit(char[] chars) { @@ -144,13 +154,23 @@ TokeniserState getState() { return state; } - void transition(TokeniserState state) { - this.state = state; + void transition(TokeniserState newState) { + // track markup / data position on state transitions + switch (newState) { + case TagOpen: + markupStartPos = reader.pos(); + break; + case Data: + if (charStartPos == Unset) // don't reset when we are jumping between e.g data -> char ref -> data + charStartPos = reader.pos(); + } + + this.state = newState; } - void advanceTransition(TokeniserState state) { + void advanceTransition(TokeniserState newState) { + transition(newState); reader.advance(); - this.state = state; } final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index f895c9ed28..902cbdf0ae 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -5,6 +5,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; +import org.jsoup.nodes.Range; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -32,6 +33,8 @@ abstract class TreeBuilder { private Token.EndTag end = new Token.EndTag(); abstract ParseSettings defaultSettings(); + private boolean trackSourceRange; // optionally tracks the source range of nodes + @ParametersAreNonnullByDefault protected void initialiseParse(Reader input, String baseUri, Parser parser) { Validate.notNull(input, "String input must not be null"); @@ -43,7 +46,8 @@ protected void initialiseParse(Reader input, String baseUri, Parser parser) { this.parser = parser; settings = parser.settings(); reader = new CharacterReader(input); - reader.trackNewlines(parser.isTrackErrors()); // when tracking errors, enable newline tracking for better error reports + trackSourceRange = parser.isTrackPosition(); + reader.trackNewlines(parser.isTrackErrors() || trackSourceRange); // when tracking errors or source ranges, enable newline tracking for better legibility currentToken = null; tokeniser = new Tokeniser(reader, parser.getErrors()); stack = new ArrayList<>(32); @@ -91,6 +95,7 @@ protected void runParser() { protected abstract boolean process(Token token); protected boolean processStartTag(String name) { + // these are "virtual" start tags (auto-created by the treebuilder), so not tracking the start position final Token.StartTag start = this.start; if (currentToken == start) { // don't recycle an in-use token return process(new Token.StartTag().name(name)); @@ -173,4 +178,37 @@ protected Tag tagFor(String tagName, ParseSettings settings) { } return tag; } + + /** + Called by implementing TreeBuilders when a node has been inserted. This implementation includes optionally tracking + the source range of the node. + * @param node the node that was just inserted + * @param token the (optional) token that created this node + */ + protected void onNodeInserted(Node node, @Nullable Token token) { + trackNodePosition(node, token, true); + } + + /** + Called by implementing TreeBuilders when a node is explicitly closed. This implementation includes optionally + tracking the closing source range of the node. + * @param node the node being closed + * @param token the end-tag token that closed this node + */ + protected void onNodeClosed(Node node, Token token) { + trackNodePosition(node, token, false); + } + + private void trackNodePosition(Node node, @Nullable Token token, boolean start) { + if (trackSourceRange && token != null) { + int startPos = token.startPos(); + if (startPos == Token.Unset) return; // untracked, virtual token + + Range.Position startRange = new Range.Position(startPos, reader.lineNumber(startPos), reader.columnNumber(startPos)); + int endPos = token.endPos(); + Range.Position endRange = new Range.Position(endPos, reader.lineNumber(endPos), reader.columnNumber(endPos)); + Range range = new Range(startRange, endRange); + range.track(node, start); + } + } } diff --git a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java index a3dc7917b5..9660723935 100644 --- a/src/main/java/org/jsoup/parser/XmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/XmlTreeBuilder.java @@ -80,6 +80,12 @@ protected boolean process(Token token) { protected void insertNode(Node node) { currentElement().appendChild(node); + onNodeInserted(node, null); + } + + protected void insertNode(Node node, Token token) { + currentElement().appendChild(node); + onNodeInserted(node, token); } Element insert(Token.StartTag startTag) { @@ -89,7 +95,7 @@ Element insert(Token.StartTag startTag) { startTag.attributes.deduplicate(settings); Element el = new Element(tag, null, settings.normalizeAttributes(startTag.attributes)); - insertNode(el); + insertNode(el, startTag); if (startTag.isSelfClosing()) { if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above. tag.setSelfClosing(); @@ -109,18 +115,18 @@ void insert(Token.Comment commentToken) { if (decl != null) insert = decl; } - insertNode(insert); + insertNode(insert, commentToken); } void insert(Token.Character token) { final String data = token.getData(); - insertNode(token.isCData() ? new CDataNode(data) : new TextNode(data)); + insertNode(token.isCData() ? new CDataNode(data) : new TextNode(data), token); } void insert(Token.Doctype d) { DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); doctypeNode.setPubSysKey(d.getPubSysKey()); - insertNode(doctypeNode); + insertNode(doctypeNode, d); } /** @@ -150,8 +156,10 @@ protected void popStackToClose(Token.EndTag endTag) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); stack.remove(pos); - if (next == firstFound) + if (next == firstFound) { + onNodeClosed(next, endTag); break; + } } } private static final int maxQueueDepth = 256; // an arbitrary tension point between real XML and crafted pain diff --git a/src/main/java/org/jsoup/parser/package-info.java b/src/main/java/org/jsoup/parser/package-info.java index 168fdf4086..f1b3c88741 100644 --- a/src/main/java/org/jsoup/parser/package-info.java +++ b/src/main/java/org/jsoup/parser/package-info.java @@ -1,4 +1,7 @@ /** Contains the HTML parser, tag specifications, and HTML tokeniser. */ +@NonnullByDefault package org.jsoup.parser; + +import org.jsoup.internal.NonnullByDefault; diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 429e925821..e18a0f1a0e 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -652,7 +652,7 @@ public void maxBodySize() throws IOException { Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute(); - int actualDocText = 269541; + int actualDocText = 269535; assertEquals(actualDocText, defaultRes.parse().text().length()); assertEquals(49165, smallRes.parse().text().length()); assertEquals(196577, mediumRes.parse().text().length()); diff --git a/src/test/java/org/jsoup/nodes/PositionTest.java b/src/test/java/org/jsoup/nodes/PositionTest.java new file mode 100644 index 0000000000..4fc3b32174 --- /dev/null +++ b/src/test/java/org/jsoup/nodes/PositionTest.java @@ -0,0 +1,180 @@ +package org.jsoup.nodes; + +import org.jsoup.Jsoup; +import org.jsoup.integration.TestServer; +import org.jsoup.integration.servlets.EchoServlet; +import org.jsoup.integration.servlets.FileServlet; +import org.jsoup.parser.Parser; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +/** + Functional tests for the Position tracking behavior (across nodes, treebuilder, etc.) + */ +class PositionTest { + static Parser TrackingParser = Parser.htmlParser().setTrackPosition(true); + + @Test void parserTrackDefaults() { + Parser htmlParser = Parser.htmlParser(); + assertFalse(htmlParser.isTrackPosition()); + htmlParser.setTrackPosition(true); + assertTrue(htmlParser.isTrackPosition()); + + Parser xmlParser = Parser.htmlParser(); + assertFalse(xmlParser.isTrackPosition()); + xmlParser.setTrackPosition(true); + assertTrue(xmlParser.isTrackPosition()); + } + + @Test void tracksPosition() { + String html = "<p id=1\n class=foo>\n<span>Hello\n &reg;\n there &copy.</span> now.\n <!-- comment --> "; + Document doc = Jsoup.parse(html, TrackingParser); + + Element body = doc.selectFirst("body"); + Element p = doc.selectFirst("p"); + Element span = doc.selectFirst("span"); + TextNode text = (TextNode) span.firstChild(); + TextNode now = (TextNode) span.nextSibling(); + Comment comment = (Comment) now.nextSibling(); + + assertFalse(body.sourceRange().isTracked()); + + Range pRange = p.sourceRange(); + assertEquals("1,1:0-2,12:19", pRange.toString()); + + // no explicit P closer + Range pEndRange = p.endSourceRange(); + assertFalse(pEndRange.isTracked()); + + Range.Position pStart = pRange.start(); + assertTrue(pStart.isTracked()); + assertEquals(0, pStart.pos()); + assertEquals(1, pStart.columnNumber()); + assertEquals(1, pStart.lineNumber()); + assertEquals("1,1:0", pStart.toString()); + + Range.Position pEnd = pRange.end(); + assertTrue(pStart.isTracked()); + assertEquals(19, pEnd.pos()); + assertEquals(12, pEnd.columnNumber()); + assertEquals(2, pEnd.lineNumber()); + assertEquals("2,12:19", pEnd.toString()); + + assertEquals("3,1:20", span.sourceRange().start().toString()); + assertEquals("3,7:26", span.sourceRange().end().toString()); + + // span end tag + Range spanEnd = span.endSourceRange(); + assertTrue(spanEnd.isTracked()); + assertEquals("5,14:52-5,21:59", spanEnd.toString()); + + String wholeText = text.getWholeText(); + assertEquals("Hello\n ®\n there ©.", wholeText); + String textOrig = "Hello\n &reg;\n there &copy."; + Range textRange = text.sourceRange(); + assertEquals(textRange.end().pos() - textRange.start().pos(), textOrig.length()); + assertEquals("3,7:26", textRange.start().toString()); + assertEquals("5,14:52", textRange.end().toString()); + + assertEquals("6,2:66", comment.sourceRange().start().toString()); + assertEquals("6,18:82", comment.sourceRange().end().toString()); + } + + @Test void tracksMarkup() { + String html = "<!doctype\nhtml>\n<title>jsoup &copy;\n2022</title><body>\n<![CDATA[\n<jsoup>\n]]>"; + Document doc = Jsoup.parse(html, TrackingParser); + + DocumentType doctype = doc.documentType(); + assertNotNull(doctype); + assertEquals("html", doctype.name()); + assertEquals("1,1:0-2,6:15", doctype.sourceRange().toString()); + + Element title = doc.selectFirst("title"); + TextNode titleText = (TextNode) title.firstChild(); + assertEquals("jsoup ©\n2022", title.text()); + assertEquals(titleText.getWholeText(), title.text()); + assertEquals("3,1:16-3,8:23", title.sourceRange().toString()); + assertEquals("3,8:23-4,5:40", titleText.sourceRange().toString()); + + CDataNode cdata = (CDataNode) doc.body().childNode(1); + assertEquals("\n<jsoup>\n", cdata.text()); + assertEquals("5,1:55-7,4:76", cdata.sourceRange().toString()); + } + + @Test void tracksDataNodes() { + String html = "<head>\n<script>foo;\nbar()\n5 <= 4;</script>"; + Document doc = Jsoup.parse(html, TrackingParser); + + Element script = doc.selectFirst("script"); + assertNotNull(script); + assertEquals("2,1:7-2,9:15", script.sourceRange().toString()); + DataNode data = (DataNode) script.firstChild(); + assertEquals("2,9:15-4,8:33", data.sourceRange().toString()); + } + + @Test void tracksXml() { + String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!doctype html>\n<rss url=foo>\nXML\n</rss>\n<!-- comment -->"; + Document doc = Jsoup.parse(xml, Parser.xmlParser().setTrackPosition(true)); + + XmlDeclaration decl = (XmlDeclaration) doc.childNode(0); + assertEquals("1,1:0-1,39:38", decl.sourceRange().toString()); + + DocumentType doctype = (DocumentType) doc.childNode(2); + assertEquals("2,1:39-2,16:54", doctype.sourceRange().toString()); + + Element rss = doc.firstElementChild(); + assertNotNull(rss); + assertEquals("3,1:55-3,14:68", rss.sourceRange().toString()); + assertEquals("5,1:73-5,7:79", rss.endSourceRange().toString()); + + TextNode text = (TextNode) rss.firstChild(); + assertNotNull(text); + assertEquals("3,14:68-5,1:73", text.sourceRange().toString()); + + Comment comment = (Comment) rss.nextSibling().nextSibling(); + assertEquals("6,1:80-6,17:96", comment.sourceRange().toString()); + } + + @BeforeAll + static void setUp() { + TestServer.start(); + } + + @AfterAll + static void tearDown() { + TestServer.stop(); + } + + @Test void tracksFromFetch() throws IOException { + String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K + Document doc = Jsoup.connect(url).parser(TrackingParser).get(); + + Element firstP = doc.selectFirst("p"); + assertNotNull(firstP); + assertEquals("4,1:53-4,4:56", firstP.sourceRange().toString()); + + Element p = doc.selectFirst("#xy"); + assertNotNull(p); + assertEquals("1000,1:279646-1000,10:279655", p.sourceRange().toString()); + assertEquals("1000,567:280212-1000,571:280216", p.endSourceRange().toString()); + + TextNode text = (TextNode) p.firstChild(); + assertEquals("1000,10:279655-1000,357:280002", text.sourceRange().toString()); + } + + @Test void tracksFromXmlFetch() throws IOException { + String url = FileServlet.urlTo("/htmltests/test-rss.xml"); + Document doc = Jsoup.connect(url).parser(Parser.xmlParser().setTrackPosition(true)).get(); + + Element item = doc.selectFirst("item + item"); + assertNotNull(item); + assertEquals("13,5:496-13,11:502", item.sourceRange().toString()); + assertEquals("17,5:779-17,12:786", item.endSourceRange().toString()); + } + +} \ No newline at end of file diff --git a/src/test/resources/htmltests/large.html b/src/test/resources/htmltests/large.html index bdf04dd320..6e418ddb08 100644 --- a/src/test/resources/htmltests/large.html +++ b/src/test/resources/htmltests/large.html @@ -997,7 +997,7 @@ <p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <i>Curabitur sodales ligula in libero</i>. Nulla facilisi. <i>Vestibulum lacinia arcu eget nulla</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. </p> -<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> +<p id=xy>Ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p> <p>VESTIBULUM tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Proin quam</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p> From 3ce79cde2b70f1fa64c95bd16778a2e61114d16f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 09:21:00 +1000 Subject: [PATCH 742/774] Bump maven-surefire-plugin from 3.0.0-M6 to 3.0.0-M7 (#1791) Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M6 to 3.0.0-M7. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M6...surefire-3.0.0-M7) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd5cbfe2d1..bffcf893ac 100644 --- a/pom.xml +++ b/pom.xml @@ -166,7 +166,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>3.0.0-M6</version> + <version>3.0.0-M7</version> <configuration> <!-- smaller stack to find stack overflows --> <argLine>-Xss256k</argLine> From 9c601ef7f9b1db4108f4c957027ac705e4422593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 09:21:18 +1000 Subject: [PATCH 743/774] Bump maven-failsafe-plugin from 3.0.0-M6 to 3.0.0-M7 (#1792) Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M6 to 3.0.0-M7. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M6...surefire-3.0.0-M7) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bffcf893ac..ba9f783565 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> - <version>3.0.0-M6</version> + <version>3.0.0-M7</version> <executions> <execution> <goals> @@ -296,7 +296,7 @@ <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> - <version>3.0.0-M6</version> + <version>3.0.0-M7</version> <executions> <execution> <goals> From fe0f9263435b44534182ddac830e6c6fd897bf92 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Jun 2022 15:35:42 +1000 Subject: [PATCH 744/774] Tidied up documentation --- src/main/java/org/jsoup/helper/Validate.java | 29 ++++++++++++++------ src/main/java/org/jsoup/nodes/Element.java | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/helper/Validate.java b/src/main/java/org/jsoup/helper/Validate.java index 97e7b67cfe..3aaa3c802e 100644 --- a/src/main/java/org/jsoup/helper/Validate.java +++ b/src/main/java/org/jsoup/helper/Validate.java @@ -3,7 +3,7 @@ import javax.annotation.Nullable; /** - * Simple validation methods. Designed for jsoup internal use + * Simple validation methods. Designed for jsoup internal use. */ public final class Validate { @@ -12,6 +12,7 @@ private Validate() {} /** * Validates that the object is not null * @param obj object to test + * @throws IllegalArgumentException if the object is null */ public static void notNull(@Nullable Object obj) { if (obj == null) @@ -21,7 +22,8 @@ public static void notNull(@Nullable Object obj) { /** * Validates that the object is not null * @param obj object to test - * @param msg message to output if validation fails + * @param msg message to include in the Exception if validation fails + * @throws IllegalArgumentException if the object is null */ public static void notNull(@Nullable Object obj, String msg) { if (obj == null) @@ -32,17 +34,19 @@ public static void notNull(@Nullable Object obj, String msg) { Verifies the input object is not null, and returns that object. Effectively this casts a nullable object to a non- null object. (Works around lack of Objects.requestNonNull in Android version.) * @param obj nullable object to case to not-null - * @return the object, or throws an NPE. + * @return the object, or throws an exception if it is null + * @throws IllegalArgumentException if the object is null */ public static Object ensureNotNull(@Nullable Object obj) { if (obj == null) - throw new NullPointerException(); + throw new IllegalArgumentException("Object must not be null"); else return obj; } /** * Validates that the value is true * @param val object to test + * @throws IllegalArgumentException if the object is not true */ public static void isTrue(boolean val) { if (!val) @@ -52,7 +56,8 @@ public static void isTrue(boolean val) { /** * Validates that the value is true * @param val object to test - * @param msg message to output if validation fails + * @param msg message to include in the Exception if validation fails + * @throws IllegalArgumentException if the object is not true */ public static void isTrue(boolean val, String msg) { if (!val) @@ -62,6 +67,7 @@ public static void isTrue(boolean val, String msg) { /** * Validates that the value is false * @param val object to test + * @throws IllegalArgumentException if the object is not false */ public static void isFalse(boolean val) { if (val) @@ -71,7 +77,8 @@ public static void isFalse(boolean val) { /** * Validates that the value is false * @param val object to test - * @param msg message to output if validation fails + * @param msg message to include in the Exception if validation fails + * @throws IllegalArgumentException if the object is not false */ public static void isFalse(boolean val, String msg) { if (val) @@ -81,6 +88,7 @@ public static void isFalse(boolean val, String msg) { /** * Validates that the array contains no null elements * @param objects the array to test + * @throws IllegalArgumentException if the array contains a null element */ public static void noNullElements(Object[] objects) { noNullElements(objects, "Array must not contain any null objects"); @@ -89,7 +97,8 @@ public static void noNullElements(Object[] objects) { /** * Validates that the array contains no null elements * @param objects the array to test - * @param msg message to output if validation fails + * @param msg message to include in the Exception if validation fails + * @throws IllegalArgumentException if the array contains a null element */ public static void noNullElements(Object[] objects, String msg) { for (Object obj : objects) @@ -100,6 +109,7 @@ public static void noNullElements(Object[] objects, String msg) { /** * Validates that the string is not null and is not empty * @param string the string to test + * @throws IllegalArgumentException if the string is null or empty */ public static void notEmpty(@Nullable String string) { if (string == null || string.length() == 0) @@ -109,7 +119,8 @@ public static void notEmpty(@Nullable String string) { /** * Validates that the string is not null and is not empty * @param string the string to test - * @param msg message to output if validation fails + * @param msg message to include in the Exception if validation fails + * @throws IllegalArgumentException if the string is null or empty */ public static void notEmpty(@Nullable String string, String msg) { if (string == null || string.length() == 0) @@ -119,6 +130,7 @@ public static void notEmpty(@Nullable String string, String msg) { /** * Blow up if we reach an unexpected state. * @param msg message to think about + * @throws IllegalStateException if we reach this state */ public static void wtf(String msg) { throw new IllegalStateException(msg); @@ -127,6 +139,7 @@ public static void wtf(String msg) { /** Cause a failure. @param msg message to output. + @throws IllegalStateException if we reach this state */ public static void fail(String msg) { throw new IllegalArgumentException(msg); diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 3b2d54aec4..2c443befb9 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -452,7 +452,7 @@ public Elements select(Evaluator evaluator) { * * @param evaluator an element evaluator * @return the first matching element (walking down the tree, starting from this element), or {@code null} if none - * matchn. + * match. */ public @Nullable Element selectFirst(Evaluator evaluator) { return Collector.findFirst(evaluator, this); From dd03d271a3ada7086c3b77603676245e30cb1ac6 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Jun 2022 15:49:58 +1000 Subject: [PATCH 745/774] Added Element.expectFirst(query) --- CHANGES | 4 ++++ src/main/java/org/jsoup/nodes/Element.java | 12 ++++++++++++ src/test/java/org/jsoup/nodes/ElementTest.java | 15 +++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/CHANGES b/CHANGES index 6014b78b8f..b24fa7f4f7 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ jsoup changelog * Improvement: added Element.firstElementChild(), Element.lastElementChild(), Node.firstChild(), Node.lastChild(), as convenient accessors to those child nodes and elements. + * Improvement: added Element.expectFirst(cssQuery), which is just like Element.selectFirst(), but instead of returning + a null if there is no match, will throw an IllegalArgumentException. This is useful if you want to simply abort + processing if an expected match is not found. + * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default internal buffer size is read. diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 2c443befb9..b8a223901f 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -441,6 +441,7 @@ public Elements select(Evaluator evaluator) { * <p>Also known as {@code querySelector()} in the Web DOM.</p> * @param cssQuery cssQuery a {@link Selector} CSS-like query * @return the first matching element, or <b>{@code null}</b> if there is no match. + * @see #expectFirst(String) */ public @Nullable Element selectFirst(String cssQuery) { return Selector.selectFirst(cssQuery, this); @@ -458,6 +459,17 @@ public Elements select(Evaluator evaluator) { return Collector.findFirst(evaluator, this); } + /** + Just like {@link #selectFirst(String)}, but if there is no match, throws an {@link IllegalArgumentException}. This + is useful if you want to simply abort processing on a failed match. + @param cssQuery a {@link Selector} CSS-like query + @return the first matching element + @throws IllegalArgumentException if no match is found + */ + public Element expectFirst(String cssQuery) { + return (Element) Validate.ensureNotNull(Selector.selectFirst(cssQuery, this)); + } + /** * Checks if this element matches the given {@link Selector} CSS query. Also knows as {@code matches()} in the Web * DOM. diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 6b06bf4e9b..b3f7c27502 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2252,6 +2252,21 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { " \n" + " "; assertEquals(expectOwn, div.child(0).wholeOwnText()); + } + + @Test void testExpectFirst() { + Document doc = Jsoup.parse("<p>One</p><p>Two <span>Three</span> <span>Four</span>"); + + Element span = doc.expectFirst("span"); + assertEquals("Three", span.text()); + assertNull(doc.selectFirst("div")); + boolean threw = false; + try { + Element div = doc.expectFirst("div"); + } catch (IllegalArgumentException e) { + threw = true; + } + assertTrue(threw); } } \ No newline at end of file From 0969fe59f27b5994e4acb0ed7f48e42ff5e0f587 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Jun 2022 15:54:15 +1000 Subject: [PATCH 746/774] Tidied up some null warnings --- .../java/org/jsoup/nodes/PositionTest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/jsoup/nodes/PositionTest.java b/src/test/java/org/jsoup/nodes/PositionTest.java index 4fc3b32174..e47817c8c5 100644 --- a/src/test/java/org/jsoup/nodes/PositionTest.java +++ b/src/test/java/org/jsoup/nodes/PositionTest.java @@ -35,12 +35,15 @@ class PositionTest { String html = "<p id=1\n class=foo>\n<span>Hello\n &reg;\n there &copy.</span> now.\n <!-- comment --> "; Document doc = Jsoup.parse(html, TrackingParser); - Element body = doc.selectFirst("body"); - Element p = doc.selectFirst("p"); - Element span = doc.selectFirst("span"); + Element body = doc.expectFirst("body"); + Element p = doc.expectFirst("p"); + Element span = doc.expectFirst("span"); TextNode text = (TextNode) span.firstChild(); + assertNotNull(text); TextNode now = (TextNode) span.nextSibling(); + assertNotNull(now); Comment comment = (Comment) now.nextSibling(); + assertNotNull(comment); assertFalse(body.sourceRange().isTracked()); @@ -94,8 +97,9 @@ class PositionTest { assertEquals("html", doctype.name()); assertEquals("1,1:0-2,6:15", doctype.sourceRange().toString()); - Element title = doc.selectFirst("title"); + Element title = doc.expectFirst("title"); TextNode titleText = (TextNode) title.firstChild(); + assertNotNull(titleText); assertEquals("jsoup ©\n2022", title.text()); assertEquals(titleText.getWholeText(), title.text()); assertEquals("3,1:16-3,8:23", title.sourceRange().toString()); @@ -110,10 +114,11 @@ class PositionTest { String html = "<head>\n<script>foo;\nbar()\n5 <= 4;</script>"; Document doc = Jsoup.parse(html, TrackingParser); - Element script = doc.selectFirst("script"); + Element script = doc.expectFirst("script"); assertNotNull(script); assertEquals("2,1:7-2,9:15", script.sourceRange().toString()); DataNode data = (DataNode) script.firstChild(); + assertNotNull(data); assertEquals("2,9:15-4,8:33", data.sourceRange().toString()); } @@ -154,16 +159,17 @@ static void tearDown() { String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K Document doc = Jsoup.connect(url).parser(TrackingParser).get(); - Element firstP = doc.selectFirst("p"); + Element firstP = doc.expectFirst("p"); assertNotNull(firstP); assertEquals("4,1:53-4,4:56", firstP.sourceRange().toString()); - Element p = doc.selectFirst("#xy"); + Element p = doc.expectFirst("#xy"); assertNotNull(p); assertEquals("1000,1:279646-1000,10:279655", p.sourceRange().toString()); assertEquals("1000,567:280212-1000,571:280216", p.endSourceRange().toString()); TextNode text = (TextNode) p.firstChild(); + assertNotNull(text); assertEquals("1000,10:279655-1000,357:280002", text.sourceRange().toString()); } @@ -171,7 +177,7 @@ static void tearDown() { String url = FileServlet.urlTo("/htmltests/test-rss.xml"); Document doc = Jsoup.connect(url).parser(Parser.xmlParser().setTrackPosition(true)).get(); - Element item = doc.selectFirst("item + item"); + Element item = doc.expectFirst("item + item"); assertNotNull(item); assertEquals("13,5:496-13,11:502", item.sourceRange().toString()); assertEquals("17,5:779-17,12:786", item.endSourceRange().toString()); From 7c21b2ecdfb3536dca7870b2ffe2093286f4086c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 15 Jun 2022 16:59:52 +1000 Subject: [PATCH 747/774] Nullable annotations --- src/main/java/org/jsoup/parser/Tokeniser.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jsoup/parser/Tokeniser.java b/src/main/java/org/jsoup/parser/Tokeniser.java index 0eb40875ee..be00b7f63c 100644 --- a/src/main/java/org/jsoup/parser/Tokeniser.java +++ b/src/main/java/org/jsoup/parser/Tokeniser.java @@ -34,19 +34,19 @@ final class Tokeniser { private final ParseErrorList errors; // errors found while tokenising private TokeniserState state = TokeniserState.Data; // current tokenisation state - private Token emitPending; // the token we are about to emit on next read + @Nullable private Token emitPending = null; // the token we are about to emit on next read private boolean isEmitPending = false; - private String charsString = null; // characters pending an emit. Will fall to charsBuilder if more than one - private StringBuilder charsBuilder = new StringBuilder(1024); // buffers characters to output as one token, if more than one emit per read + @Nullable private String charsString = null; // characters pending an emit. Will fall to charsBuilder if more than one + private final StringBuilder charsBuilder = new StringBuilder(1024); // buffers characters to output as one token, if more than one emit per read StringBuilder dataBuffer = new StringBuilder(1024); // buffers data looking for </script> - Token.Tag tagPending; // tag we are building up Token.StartTag startPending = new Token.StartTag(); Token.EndTag endPending = new Token.EndTag(); + Token.Tag tagPending = startPending; // tag we are building up: start or end pending Token.Character charPending = new Token.Character(); Token.Doctype doctypePending = new Token.Doctype(); // doctype building up Token.Comment commentPending = new Token.Comment(); // comment building up - private String lastStartTag; // the last start tag emitted, to test appropriate end tag + @Nullable private String lastStartTag; // the last start tag emitted, to test appropriate end tag @Nullable private String lastStartCloseSeq; // "</" + lastStartTag, so we can quickly check for that in RCData private static final int Unset = -1; @@ -76,6 +76,7 @@ Token read() { return token; } else { isEmitPending = false; + assert emitPending != null; return emitPending; } } @@ -175,7 +176,7 @@ void advanceTransition(TokeniserState newState) { final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays final private int[] multipointHolder = new int[2]; - @Nullable int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { + @Nullable int[] consumeCharacterReference(@Nullable Character additionalAllowedCharacter, boolean inAttribute) { if (reader.isEmpty()) return null; if (additionalAllowedCharacter != null && additionalAllowedCharacter == reader.current()) @@ -292,7 +293,7 @@ boolean isAppropriateEndTagToken() { return lastStartTag != null && tagPending.name().equalsIgnoreCase(lastStartTag); } - String appropriateEndTagName() { + @Nullable String appropriateEndTagName() { return lastStartTag; // could be null } From e714ef12fab4fd00cf7133a22fba4a71ccf7af8e Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 17 Jun 2022 18:58:25 +1000 Subject: [PATCH 748/774] Improved newline and whitespace normalization Fixes #1787 In TextNode, if this is a blank, check the next Element if it will indent. If so, can skip. Previously would check if the parent el would indent, which is wrong for inline elements. Also made empty tags (like <img>) not indent, but inline. And fixed up how whitespace is normalized at the end of an element, and after the body tag. --- CHANGES | 5 ++++ src/main/java/org/jsoup/nodes/Element.java | 1 - src/main/java/org/jsoup/nodes/TextNode.java | 27 ++++++++++++++----- .../org/jsoup/parser/HtmlTreeBuilder.java | 8 ++++++ .../jsoup/parser/HtmlTreeBuilderState.java | 15 +++-------- .../java/org/jsoup/nodes/DocumentTest.java | 14 +++++++--- .../java/org/jsoup/nodes/ElementTest.java | 17 +++++++++++- .../java/org/jsoup/parser/HtmlParserTest.java | 23 ++++++++-------- .../parser/HtmlTreeBuilderStateTest.java | 4 +-- .../org/jsoup/parser/XmlTreeBuilderTest.java | 2 +- .../java/org/jsoup/safety/CleanerTest.java | 8 +++--- .../java/org/jsoup/select/ElementsTest.java | 13 ++++++--- src/test/resources/htmltests/medium.html | 12 ++++----- 13 files changed, 97 insertions(+), 52 deletions(-) diff --git a/CHANGES b/CHANGES index b24fa7f4f7..47fd71e2a5 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,11 @@ jsoup changelog what should have been preformatted output to instead be a run of text. <https://github.com/jhy/jsoup/issues/1776> + * Bugfix: when pretty-print serializing HTML, newlines separating phrasing content (e.g. a <span> tag within a <p> tag + would be incorrectly skipped, instead of normalized to a space. Additionally, improved space normalization between + other end of line occurences, and whitespace handling after a closing </body> + <https://github.com/jhy/jsoup/issues/1787> + *** Release 1.15.1 [2022-May-15] * Change: removed previously deprecated methods and classes (including org.jsoup.safety.Whitelist; use org.jsoup.safety.Safelist instead). diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index b8a223901f..e1ab957e71 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -1842,7 +1842,6 @@ private boolean isFormatAsBlock(Document.OutputSettings out) { private boolean isInlineable(Document.OutputSettings out) { return tag().isInline() - && !tag().isEmpty() && (parent() == null || parent().isBlock()) && previousSibling() != null && !out.outline(); diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index c277da22b3..e32a0dbf13 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -1,7 +1,7 @@ package org.jsoup.nodes; -import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; +import org.jsoup.internal.StringUtil; import java.io.IOException; @@ -80,17 +80,30 @@ public TextNode splitText(int offset) { return tailNode; } - void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { final boolean prettyPrint = out.prettyPrint(); final Element parent = parentNode instanceof Element ? ((Element) parentNode) : null; - final boolean parentIndent = parent != null && parent.shouldIndent(out); final boolean blank = isBlank(); final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); - if (normaliseWhite && parentIndent && StringUtil.startsWithNewline(coreValue()) && blank) // we are skippable whitespace - return; - - if (prettyPrint && ((siblingIndex == 0 && parent != null && parent.tag().formatAsBlock() && !blank) || (out.outline() && siblingNodes().size()>0 && !blank) )) + // if this text is just whitespace, and the next node will cause an indent, skip this text: + if (normaliseWhite && blank) { + boolean canSkip = false; + Node next = this.nextSibling(); + if (next instanceof Element) { + Element nextEl = (Element) next; + canSkip = nextEl.shouldIndent(out); + } else if (next == null && parent != null) { // we are the last child, check parent + canSkip = parent.shouldIndent(out); + } else if (next instanceof TextNode && (((TextNode) next).isBlank())) { + // sometimes get a run of textnodes from parser if nodes are re-parented + canSkip = true; + } + if (canSkip) + return; + } + + if (prettyPrint && ((siblingIndex == 0 && parent != null && parent.tag().formatAsBlock() && !blank) || (out.outline() && siblingNodes().size() > 0 && !blank))) indent(accum, depth, out); final boolean stripWhite = prettyPrint && parentNode instanceof Document; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java index 58760d1679..7a9c0be3c7 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilder.java @@ -558,6 +558,14 @@ boolean resetInsertionMode() { return state != origState; } + /** Places the body back onto the stack and moves to InBody, for cases in AfterBody / AfterAfterBody when more content comes */ + void resetBody() { + if (!onStack("body")) { + stack.add(doc.body()); + } + transition(HtmlTreeBuilderState.InBody); + } + // todo: tidy up in specific scope methods private String[] specificScopeTarget = {null}; diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index 57e42d4dfc..e1f33a4303 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -718,6 +718,7 @@ private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) { return false; } else { // todo: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr, body, html + anyOtherEndTag(t, tb); tb.transition(AfterBody); } break; @@ -1597,13 +1598,14 @@ boolean process(Token t, HtmlTreeBuilder tb) { tb.error(this); return false; } else { + if (tb.onStack("html")) tb.popStackToClose("html"); tb.transition(AfterAfterBody); } } else if (t.isEOF()) { // chillax! we're done } else { tb.error(this); - tb.transition(InBody); + tb.resetBody(); return tb.process(t); } return true; @@ -1688,21 +1690,12 @@ boolean process(Token t, HtmlTreeBuilder tb) { } else if (t.isDoctype() || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { return tb.process(t, InBody); } else if (isWhitespace(t)) { - // allows space after </html>, and put the body back on stack to allow subsequent tags if any - // todo - might be better for </body> and </html> to close them, allow trailing space, and then reparent - // that space into body if other tags get re-added. but that's overkill for now - Element html = tb.popStackToClose("html"); tb.insert(t.asCharacter()); - if (html != null) { - tb.stack.add(html); - Element body = html.selectFirst("body"); - if (body != null) tb.stack.add(body); - } }else if (t.isEOF()) { // nice work chuck } else { tb.error(this); - tb.transition(InBody); + tb.resetBody(); return tb.process(t); } return true; diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index 47b7ccbede..a490b8ea16 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -109,11 +109,17 @@ public class DocumentTest { Document doc = Jsoup.parse("<title>Hello</title> <p>One<p>Two"); Document clone = doc.clone(); - assertEquals("<html><head><title>Hello</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p><p>Two</p></body></html>", TextUtil.stripNewlines(clone.html())); + assertEquals("<html><head><title>Hello</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p><p>Two</p></body></html>", TextUtil.stripNewlines(clone.html())); clone.title("Hello there"); - clone.select("p").first().text("One more").attr("id", "1"); - assertEquals("<html><head><title>Hello there</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p id=\"1\">One more</p><p>Two</p></body></html>", TextUtil.stripNewlines(clone.html())); - assertEquals("<html><head><title>Hello</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p><p>Two</p></body></html>", TextUtil.stripNewlines(doc.html())); + clone.expectFirst("p").text("One more").attr("id", "1"); + assertEquals("<html><head><title>Hello there</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p id=\"1\">One more</p><p>Two</p></body></html>", TextUtil.stripNewlines(clone.html())); + assertEquals("<html><head><title>Hello</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><p>One</p><p>Two</p></body></html>", TextUtil.stripNewlines(doc.html())); + } + + @Test void testBasicIndent() { + Document doc = Jsoup.parse("<title>Hello</title> <p>One<p>Two"); + String expect = "<html>\n <head>\n <title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body>\n <p>One</p>\n <p>Two</p>\n </body>\n</html>"; + assertEquals(expect, doc.html()); } @Test public void testClonesDeclarations() { diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index b3f7c27502..8b49237bd2 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -536,7 +536,7 @@ public void testContainerOutput() { Document doc = Jsoup.parse("<title>Hello there</title> <div><p>Hello</p><p>there</p></div> <div>Another</div>"); assertEquals("<title>Hello there</title>", doc.select("title").first().outerHtml()); assertEquals("<div>\n <p>Hello</p>\n <p>there</p>\n</div>", doc.select("div").first().outerHtml()); - assertEquals("<div>\n <p>Hello</p>\n <p>there</p>\n</div> \n<div>\n Another\n</div>", doc.select("body").first().html()); + assertEquals("<div>\n <p>Hello</p>\n <p>there</p>\n</div>\n<div>\n Another\n</div>", doc.select("body").first().html()); } @Test @@ -2269,4 +2269,19 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { } assertTrue(threw); } + + @Test void spanRunsMaintainSpace() { + // https://github.com/jhy/jsoup/issues/1787 + Document doc = Jsoup.parse("<p><span>One</span>\n<span>Two</span>\n<span>Three</span></p>"); + String text = "One Two Three"; + Element body = doc.body(); + assertEquals(text, body.text()); + + Element p = doc.expectFirst("p"); + String html = p.html(); + p.html(html); + assertEquals(text, body.text()); + + assertEquals("<p><span>One</span> <span>Two</span> <span>Three</span></p>", body.html()); + } } \ No newline at end of file diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index fd0f1d14ca..93c4209f6e 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -273,13 +273,13 @@ public class HtmlParserTest { @Test public void handlesNestedImplicitTable() { Document doc = Jsoup.parse("<table><td>1</td></tr> <td>2</td></tr> <td> <table><td>3</td> <td>4</td></table> <tr><td>5</table>"); - assertEquals("<table><tbody><tr><td>1</td></tr> <tr><td>2</td></tr> <tr><td> <table><tbody><tr><td>3</td> <td>4</td></tr></tbody></table> </td></tr><tr><td>5</td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<table><tbody><tr><td>1</td></tr><tr><td>2</td></tr><tr><td><table><tbody><tr><td>3</td><td>4</td></tr></tbody></table></td></tr><tr><td>5</td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); } @Test public void handlesWhatWgExpensesTableExample() { // http://www.whatwg.org/specs/web-apps/current-work/multipage/tabular-data.html#examples-0 Document doc = Jsoup.parse("<table> <colgroup> <col> <colgroup> <col> <col> <col> <thead> <tr> <th> <th>2008 <th>2007 <th>2006 <tbody> <tr> <th scope=rowgroup> Research and development <td> $ 1,109 <td> $ 782 <td> $ 712 <tr> <th scope=row> Percentage of net sales <td> 3.4% <td> 3.3% <td> 3.7% <tbody> <tr> <th scope=rowgroup> Selling, general, and administrative <td> $ 3,761 <td> $ 2,963 <td> $ 2,433 <tr> <th scope=row> Percentage of net sales <td> 11.6% <td> 12.3% <td> 12.6% </table>"); - assertEquals("<table> <colgroup> <col> </colgroup><colgroup> <col> <col> <col> </colgroup><thead> <tr> <th> </th><th>2008 </th><th>2007 </th><th>2006 </th></tr></thead><tbody> <tr> <th scope=\"rowgroup\"> Research and development </th><td> $ 1,109 </td><td> $ 782 </td><td> $ 712 </td></tr><tr> <th scope=\"row\"> Percentage of net sales </th><td> 3.4% </td><td> 3.3% </td><td> 3.7% </td></tr></tbody><tbody> <tr> <th scope=\"rowgroup\"> Selling, general, and administrative </th><td> $ 3,761 </td><td> $ 2,963 </td><td> $ 2,433 </td></tr><tr> <th scope=\"row\"> Percentage of net sales </th><td> 11.6% </td><td> 12.3% </td><td> 12.6% </td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<table><colgroup><col></colgroup><colgroup><col><col><col></colgroup><thead><tr><th></th><th>2008 </th><th>2007 </th><th>2006 </th></tr></thead><tbody><tr><th scope=\"rowgroup\"> Research and development </th><td> $ 1,109 </td><td> $ 782 </td><td> $ 712 </td></tr><tr><th scope=\"row\"> Percentage of net sales </th><td> 3.4% </td><td> 3.3% </td><td> 3.7% </td></tr></tbody><tbody><tr><th scope=\"rowgroup\"> Selling, general, and administrative </th><td> $ 3,761 </td><td> $ 2,963 </td><td> $ 2,433 </td></tr><tr><th scope=\"row\"> Percentage of net sales </th><td> 11.6% </td><td> 12.3% </td><td> 12.6% </td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); } @Test public void handlesTbodyTable() { @@ -294,7 +294,7 @@ public class HtmlParserTest { @Test public void noTableDirectInTable() { Document doc = Jsoup.parse("<table> <td>One <td><table><td>Two</table> <table><td>Three"); - assertEquals("<table> <tbody><tr><td>One </td><td><table><tbody><tr><td>Two</td></tr></tbody></table> <table><tbody><tr><td>Three</td></tr></tbody></table></td></tr></tbody></table>", + assertEquals("<table><tbody><tr><td>One </td><td><table><tbody><tr><td>Two</td></tr></tbody></table><table><tbody><tr><td>Three</td></tr></tbody></table></td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); } @@ -472,7 +472,7 @@ public class HtmlParserTest { // if a known tag, allow self closing outside of spec, but force an end tag. unknown tags can be self closing. String h = "<div id='1' /><script src='/foo' /><div id=2><img /><img></div><a id=3 /><i /><foo /><foo>One</foo> <hr /> hr text <hr> hr text two"; Document doc = Jsoup.parse(h); - assertEquals("<div id=\"1\"></div><script src=\"/foo\"></script><div id=\"2\"><img><img></div><a id=\"3\"></a><i></i><foo /><foo>One</foo> <hr> hr text <hr> hr text two", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<div id=\"1\"></div><script src=\"/foo\"></script><div id=\"2\"><img><img></div><a id=\"3\"></a><i></i><foo /><foo>One</foo><hr> hr text <hr> hr text two", TextUtil.stripNewlines(doc.body().html())); } @Test public void handlesKnownEmptyNoFrames() { @@ -599,7 +599,7 @@ public class HtmlParserTest { @Test public void testHgroup() { // jsoup used to not allow hgroup in h{n}, but that's not in spec, and browsers are OK Document doc = Jsoup.parse("<h1>Hello <h2>There <hgroup><h1>Another<h2>headline</hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup>"); - assertEquals("<h1>Hello </h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<h1>Hello </h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup><hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", TextUtil.stripNewlines(doc.body().html())); } @Test public void testRelaxedTags() { @@ -611,7 +611,7 @@ public class HtmlParserTest { // h* tags (h1 .. h9) in browsers can handle any internal content other than other h*. which is not per any // spec, which defines them as containing phrasing content only. so, reality over theory. Document doc = Jsoup.parse("<h1>Hello <div>There</div> now</h1> <h2>More <h3>Content</h3></h2>"); - assertEquals("<h1>Hello <div>There</div> now</h1> <h2>More </h2><h3>Content</h3>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<h1>Hello <div>There</div> now</h1><h2>More </h2><h3>Content</h3>", TextUtil.stripNewlines(doc.body().html())); } @Test public void testSpanContents() { @@ -720,7 +720,7 @@ public class HtmlParserTest { // and the <i> inside the table and does not leak out. String h = "<p><b>One</p> <table><tr><td><p><i>Three<p>Four</i></td></tr></table> <p>Five</p>"; Document doc = Jsoup.parse(h); - String want = "<p><b>One</b></p><b> \n" + + String want = "<p><b>One</b></p><b>\n" + " <table>\n" + " <tbody>\n" + " <tr>\n" + @@ -1246,7 +1246,7 @@ public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/comments.html"); Document doc = Jsoup.parse(in, "UTF-8"); - assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><!-- now --> <head><!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", + assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", StringUtil.normaliseWhitespace(doc.html())); assertEquals("A Certain Kind of Test", doc.head().select("title").text()); @@ -1399,15 +1399,14 @@ public void testUNewlines() { String html = "\n<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n</body>\n</html>\n"; Document doc = Jsoup.parse(html); doc.outputSettings().prettyPrint(false); - assertEquals("<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n\n</body></html>\n", doc.outerHtml()); + assertEquals("<!doctype html>\n<html>\n<head>\n<title>Hello</title>\n <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n<body>\n<p>One</p>\n</body>\n</html>\n", doc.outerHtml()); } @Test public void handleContentAfterBody() { String html = "<body>One</body> <p>Hello!</p></html> <p>There</p>"; - // todo - ideally would move that space afer /html to the body when the There <p> is seen Document doc = Jsoup.parse(html); doc.outputSettings().prettyPrint(false); - assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One <p>Hello!</p><p>There</p></body></html> ", doc.outerHtml()); + assertEquals("<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>One<p>Hello!</p><p>There</p></body> </html> ", doc.outerHtml()); } @Test public void preservesTabs() { @@ -1487,7 +1486,7 @@ private boolean didAddElements(String input) { String html = "<a>\n<b>\n<div>\n<a>test</a>\n</div>\n</b>\n</a>"; Document doc = Jsoup.parse(html); assertNotNull(doc); - assertEquals("<a><b> </b></a><b><div><a></a><a>test</a></div> </b>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<a> <b> </b></a><b><div><a></a><a>test</a></div> </b>", TextUtil.stripNewlines(doc.body().html())); } @Test public void tagsMustStartWithAscii() { diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index e92c610ab3..c5dbbfc169 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -75,7 +75,7 @@ public void nestedAnchorElements01() { String s = Jsoup.parse(html).toString(); assertEquals("<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body><a href=\"#1\"> </a>\n" + + " <body> <a href=\"#1\"> </a>\n" + " <div>\n" + " <a href=\"#1\"></a><a href=\"#2\">child</a>\n" + " </div>\n" + @@ -99,7 +99,7 @@ public void nestedAnchorElements02() { String s = Jsoup.parse(html).toString(); assertEquals("<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body><a href=\"#1\"> </a>\n" + + " <body> <a href=\"#1\"> </a>\n" + " <div>\n" + " <a href=\"#1\"></a>\n" + " <div>\n" + diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java index c254e8e9cf..98196a0b54 100644 --- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java +++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java @@ -94,7 +94,7 @@ public void testSupplyParserToDataStream() throws IOException, URISyntaxExceptio public void testDoesNotForceSelfClosingKnownTags() { // html will force "<br>one</br>" to logically "<br />One<br />". XML should be stay "<br>one</br> -- don't recognise tag. Document htmlDoc = Jsoup.parse("<br>one</br>"); - assertEquals("<br>one\n<br>", htmlDoc.body().html()); + assertEquals("<br>one<br>", htmlDoc.body().html()); Document xmlDoc = Jsoup.parse("<br>one</br>", "", Parser.xmlParser()); assertEquals("<br>one</br>", xmlDoc.html()); diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index f73e462ba4..8ae21d2019 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -195,13 +195,13 @@ public void safeListedProtocolShouldBeRetained(Locale locale) { @Test public void resolvesRelativeLinks() { String html = "<a href='/foo'>Link</a><img src='/bar'>"; String clean = Jsoup.clean(html, "http://example.com/", Safelist.basicWithImages()); - assertEquals("<a href=\"http://example.com/foo\" rel=\"nofollow\">Link</a>\n<img src=\"http://example.com/bar\">", clean); + assertEquals("<a href=\"http://example.com/foo\" rel=\"nofollow\">Link</a><img src=\"http://example.com/bar\">", clean); } @Test public void preservesRelativeLinksIfConfigured() { String html = "<a href='/foo'>Link</a><img src='/bar'> <img src='javascript:alert()'>"; String clean = Jsoup.clean(html, "http://example.com/", Safelist.basicWithImages().preserveRelativeLinks(true)); - assertEquals("<a href=\"/foo\" rel=\"nofollow\">Link</a>\n<img src=\"/bar\"> \n<img>", clean); + assertEquals("<a href=\"/foo\" rel=\"nofollow\">Link</a><img src=\"/bar\"> <img>", clean); } @Test public void dropsUnresolvableRelativeLinks() { @@ -213,10 +213,10 @@ public void safeListedProtocolShouldBeRetained(Locale locale) { @Test public void handlesCustomProtocols() { String html = "<img src='cid:12345' /> <img src='data:gzzt' />"; String dropped = Jsoup.clean(html, Safelist.basicWithImages()); - assertEquals("<img> \n<img>", dropped); + assertEquals("<img> <img>", dropped); String preserved = Jsoup.clean(html, Safelist.basicWithImages().addProtocols("img", "src", "cid", "data")); - assertEquals("<img src=\"cid:12345\"> \n<img src=\"data:gzzt\">", preserved); + assertEquals("<img src=\"cid:12345\"> <img src=\"data:gzzt\">", preserved); } @Test public void handlesAllPseudoTag() { diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java index 20399e2e22..eab17c2c0e 100644 --- a/src/test/java/org/jsoup/select/ElementsTest.java +++ b/src/test/java/org/jsoup/select/ElementsTest.java @@ -2,7 +2,13 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; -import org.jsoup.nodes.*; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.FormElement; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; import org.junit.jupiter.api.Test; import java.util.List; @@ -178,8 +184,9 @@ public class ElementsTest { String h = "<p><b>This</b> is <b>jsoup</b>.</p> <p>How do you like it?</p>"; Document doc = Jsoup.parse(h); doc.select("p").wrap("<div></div>"); - assertEquals("<div><p><b>This</b> is <b>jsoup</b>.</p></div> <div><p>How do you like it?</p></div>", - TextUtil.stripNewlines(doc.body().html())); + assertEquals( + "<div>\n <p><b>This</b> is <b>jsoup</b>.</p>\n</div>\n<div>\n <p>How do you like it?</p>\n</div>", + doc.body().html()); } @Test public void unwrap() { diff --git a/src/test/resources/htmltests/medium.html b/src/test/resources/htmltests/medium.html index b228f2ae69..1f90df242e 100644 --- a/src/test/resources/htmltests/medium.html +++ b/src/test/resources/htmltests/medium.html @@ -1,10 +1,10 @@ -<html> - <head> - <title>Large HTML</title> - <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> - <body> +<html> + <head> + <title>Large HTML</title> + <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> + <body> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero.</p> - <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> <p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna.</p> <p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi.</p> <p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque.</p> From 8733445256f4b47d53f2ff855f717314cf2448f9 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 19 Jun 2022 10:32:55 +1000 Subject: [PATCH 749/774] Fixed an OOB in TreeBuilder when getting the body Element Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48116 Caused by: e714ef12fab4fd00cf7133a22fba4a71ccf7af8e --- .../org/jsoup/parser/HtmlTreeBuilderState.java | 5 +++-- src/test/resources/fuzztests/48116.html.gz | Bin 0 -> 9078 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/fuzztests/48116.html.gz diff --git a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java index e1f33a4303..354b2170aa 100644 --- a/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java +++ b/src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java @@ -385,8 +385,9 @@ private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) { return false; // ignore } else { tb.framesetOk(false); - Element body = stack.get(1); - if (startTag.hasAttributes()) { + // will be on stack if this is a nested body. won't be if closed (which is a variance from spec, which leaves it on) + Element body; + if (startTag.hasAttributes() && (body = tb.getFromStack("body")) != null) { // we only ever put one body on stack for (Attribute attribute : startTag.attributes) { if (!body.hasAttr(attribute.getKey())) body.attributes().put(attribute); diff --git a/src/test/resources/fuzztests/48116.html.gz b/src/test/resources/fuzztests/48116.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..37367dc8cc2067689c67531dac9a14b35255065b GIT binary patch literal 9078 zcmeGhJ8T?9bkCx}IwPf`0qKI!A&jiWmX#vB8{;G>4VEGVReUHrw`1SH{<QlJ-BBc@ zi$qD}1_}xqR|p|KO+@(hL1|Gs3RfvmDg?)y+0X6X?p@+oCL!9{y_<P&=kwm%H}5kW zfW<q}X@35=%UjP_@7Y)G-TMC5d%x}3)}py!j}}eopN5@q(-o0G(`4WDf4{K`%bq_7 z7?^!@9k3Q(Fku)2<^<dNuw^o^9=y71+sTy7XaJ)0Y&dzb+wC$&_1SZqZ*LN$+Ffwu z*>hc%&JD;sI1voEBYV!xkAVC3h&c>}1su?Z&43L$1E9+fkp2e{{}e`D9fIkyI1j0o zwuYMd@>~L-oyRtt{La^+h*1#HOl1m>!u<+x8zSL|9s*G!eGg2kcf(kk_ies>-yV|= z8@d^VGC)6=v5XOyhGBe{G$C>=$6<zH`t&aRfcv2>LfxE2=8Hsq1qYYr@il`UT5$Fg z+?^6=1-Si2m+n~lr<R({c3)Vw90gmsl5l!+k#7L7Pda^R+rne22Q~WWyzT`9d>%)E zpl7*(TU%8_-E64GKo-WTd1+>lYQiO&5bby^g^B#=;)Y&%@?3Oig}AGTdWx$2vxLyY zpfn4mMRFV$*i%5lVyQwXU5+qePqy1_P*wn>C27m<f{79x(F9!q?EQReJa$C_A|DB; zM$Q;7k!Dk~mq<DiB2R0|#)L+q-LpP`c<q=W#(tcX$OPYWqg@f<AQYjO0Vz%2q7~xM zC3U91R?CE-W!^E7jMvJ_im|d%Y6zQYbhs;UJU(xG*TLj;)7DeJkOkxM_+9BiKV-HT zA_-Kufnzm{r=MklLkGzKuQ9(547j&$TL^yv4<Uf0BZ?)!5R{EA{)Q=teHgWrS%;qQ zz!|ck%7Eu<Wmi);<@7(O&wRY_hoQ+yq{(*R0+3)$670e|!x+$_Rx*J8I+LD2izG(H zudovdO7#3-^ix_0`DyYXZ%4A>^Er*pMY7k*W+1nhl_gN7WoBR6_S#}h`osq0K?Y{F zCbvRrex^~nFCy>X{ozJ-`@W=h<EbkrdT}|}|I!<T&W<C4gZ<A$cU!~;)*Uz~14kU} zlg6>y1($u`AzW}yw`vz$yWpHUOqwMJ4CEP8-&MQd)E}n;do(V1{Rwr!*J>B60`te( z1(O%1TSbhos;JS`p(8>>shlG&*?td4Dr7{9VnDPwh1Kzhs&Thzu!<LhEPSLnhQC}3 zX{5si#h$=hZ?fyFDzas-R806cFjt@mxz$mDECtM5?D(M=1QeW#Z0dY4q!N&+s0jEd zj+Al%R3UsS1~=KX#HIkY=6hcV{=WWGA-$&g6clOh`Ej5)9yg4u+Ioz~|D-6Ym6XB( zAH|}L#X3hkCm&8cS*XwHxI@2@Sb#;cV{vx#sR3tn;!(}FM9VV_5`=nHF%Pqu*Bm=Z zzL9%xMA)*{BuybgWAaLKG|$qj%k%V+*i5FV8cr!q2Pu!DjEHiZ&P$YOuIu1F$#&)g zappxCf+Jh2jM9Y5I!(Ah2MnpFpv+A~e|~=T7(7LBml2nf9XUsJrgxS~`-(Fqs&Vvv zzLwaB+W_1wYR?h(c}^+Hin;2079E4ALok!CtGBPM)Et>S^mfW?No%>&=AL-1%~>9u z0GSq6ZM-d}w@gXto6Y(xuyj~gp9NO9uq@dxxUl*xus#bs&a=QLH4&-L0`<p)$2t+I wKgX56=+$R|_2)RuB7v&!dMa`#mGm%cx~|Ux{|BGrY8U+IKF3XOn8U*V0^)t0Qvd(} literal 0 HcmV?d00001 From 67b48ddbcd3ed055d5130141235697cea24c69d1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 19 Jun 2022 11:20:43 +1000 Subject: [PATCH 750/774] Pretty-print doctypes on a newline Fixes #1664 --- CHANGES | 3 +++ .../java/org/jsoup/nodes/DocumentType.java | 4 ++++ .../java/org/jsoup/nodes/ElementTest.java | 21 +++++++++++++++++++ .../java/org/jsoup/parser/HtmlParserTest.java | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 47fd71e2a5..28d545582e 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ jsoup changelog a null if there is no match, will throw an IllegalArgumentException. This is useful if you want to simply abort processing if an expected match is not found. + * Improvement: when pretty-printing HTML, doctypes are emitted on a newline if there is a preceeding comment. + <https://github.com/jhy/jsoup/pull/1664> + * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default internal buffer size is read. diff --git a/src/main/java/org/jsoup/nodes/DocumentType.java b/src/main/java/org/jsoup/nodes/DocumentType.java index 6c181e522b..b2c2dc6623 100644 --- a/src/main/java/org/jsoup/nodes/DocumentType.java +++ b/src/main/java/org/jsoup/nodes/DocumentType.java @@ -78,6 +78,10 @@ public String nodeName() { @Override void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { + // add a newline if the doctype has a preceding node (which must be a comment) + if (siblingIndex > 0 && out.prettyPrint()) + accum.append('\n'); + if (out.syntax() == Syntax.html && !has(PUBLIC_ID) && !has(SYSTEM_ID)) { // looks like a html5 doctype, go lowercase for aesthetics accum.append("<!doctype"); diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 8b49237bd2..b2701e8441 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2284,4 +2284,25 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { assertEquals("<p><span>One</span> <span>Two</span> <span>Three</span></p>", body.html()); } + + @Test void doctypeIsPrettyPrinted() { + // resolves underlying issue raised in https://github.com/jhy/jsoup/pull/1664 + Document doc1 = Jsoup.parse("<!--\nlicense\n-->\n \n<!doctype html>\n<html>"); + Document doc2 = Jsoup.parse("\n <!doctype html><html>"); + Document doc3 = Jsoup.parse("<!doctype html>\n<html>"); + Document doc4 = Jsoup.parse("\n<!doctype html>\n<html>"); + Document doc5 = Jsoup.parse("\n<!--\n comment \n --> <!doctype html>\n<html>"); + Document doc6 = Jsoup.parse("<!--\n comment \n --> <!doctype html>\n<html>"); + + assertEquals("<!--\nlicense\n-->\n<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc1.html()); + doc1.outputSettings().prettyPrint(false); + assertEquals("<!--\nlicense\n--><!doctype html>\n<html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body></body></html>", doc1.html()); + // note that the whitespace between the comment and the doctype is not retained, in Initial state + + assertEquals("<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc2.html()); + assertEquals("<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc3.html()); + assertEquals("<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc4.html()); + assertEquals("<!--\n comment \n -->\n<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc5.html()); + assertEquals("<!--\n comment \n -->\n<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc6.html()); + } } \ No newline at end of file diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index 93c4209f6e..dd1ecc36e4 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -1246,7 +1246,7 @@ public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/comments.html"); Document doc = Jsoup.parse(in, "UTF-8"); - assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", + assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --> <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", StringUtil.normaliseWhitespace(doc.html())); assertEquals("A Certain Kind of Test", doc.head().select("title").text()); From fc41ec98eaf2a9f803602c4d2dea6703edbdf4c7 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Fri, 24 Jun 2022 16:32:10 +1000 Subject: [PATCH 751/774] Trim leading and trailing spaces in blocks when appropriate Improved whitespace and indenting behaviour when pretty printing HTML. This corrects the indenting level of textnodes (that were indented in the original source), and improves the round-trip parse/output of pretty-printed HTML. Trims leading and trailing whitespace for block elements when that whitespace is not part of the text flow. As an implementation note: the pretty-printing code is getting too dispersed in the various node methods. Particularly, some aspects are not symmetric (e.g. elements and textnode indent, or head and tail). It would be a useful refactoring to make a pretty-print class and contain all this logic, and make the traversor use that and include a little more state (if last indented, if this is meaningful whitespace, etc). Fixes #1798 --- CHANGES | 6 +++- src/main/java/org/jsoup/nodes/Attribute.java | 2 +- src/main/java/org/jsoup/nodes/Entities.java | 17 ++++++--- src/main/java/org/jsoup/nodes/TextNode.java | 36 +++++++++---------- .../java/org/jsoup/nodes/XmlDeclaration.java | 2 +- .../java/org/jsoup/nodes/DocumentTest.java | 6 ++-- .../java/org/jsoup/nodes/ElementTest.java | 29 +++++++++++++++ .../java/org/jsoup/parser/HtmlParserTest.java | 19 +++++----- .../parser/HtmlTreeBuilderStateTest.java | 10 +++--- .../java/org/jsoup/select/TraversorTest.java | 2 +- src/test/resources/htmltests/medium.html | 4 +-- 11 files changed, 86 insertions(+), 47 deletions(-) diff --git a/CHANGES b/CHANGES index 28d545582e..33ed77eb49 100644 --- a/CHANGES +++ b/CHANGES @@ -12,9 +12,13 @@ jsoup changelog a null if there is no match, will throw an IllegalArgumentException. This is useful if you want to simply abort processing if an expected match is not found. - * Improvement: when pretty-printing HTML, doctypes are emitted on a newline if there is a preceeding comment. + * Improvement: when pretty-printing HTML, doctypes are emitted on a newline if there is a preceding comment. <https://github.com/jhy/jsoup/pull/1664> + * Improvement: when pretty-printing, trim the leading and trailing spaces of textnodes in block tags when possible, + so that they are indented correctly. + <https://github.com/jhy/jsoup/issues/1798> + * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default internal buffer size is read. diff --git a/src/main/java/org/jsoup/nodes/Attribute.java b/src/main/java/org/jsoup/nodes/Attribute.java index 4106bd8a0f..56a34b49bf 100644 --- a/src/main/java/org/jsoup/nodes/Attribute.java +++ b/src/main/java/org/jsoup/nodes/Attribute.java @@ -142,7 +142,7 @@ static void htmlNoValidate(String key, @Nullable String val, Appendable accum, D accum.append(key); if (!shouldCollapseAttribute(key, val, out)) { accum.append("=\""); - Entities.escape(accum, Attributes.checkNotNull(val) , out, true, false, false); + Entities.escape(accum, Attributes.checkNotNull(val) , out, true, false, false, false); accum.append('"'); } } diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java index 732bf34d1e..d330005d79 100644 --- a/src/main/java/org/jsoup/nodes/Entities.java +++ b/src/main/java/org/jsoup/nodes/Entities.java @@ -142,7 +142,7 @@ public static String escape(String string, OutputSettings out) { return ""; StringBuilder accum = StringUtil.borrowBuilder(); try { - escape(accum, string, out, false, false, false); + escape(accum, string, out, false, false, false, false); } catch (IOException e) { throw new SerializationException(e); // doesn't happen } @@ -160,9 +160,9 @@ public static String escape(String string) { return escape(string, DefaultOutput); } - // this method is ugly, and does a lot. but other breakups cause rescanning and stringbuilder generations + // this method does a lot, but other breakups cause rescanning and stringbuilder generations static void escape(Appendable accum, String string, OutputSettings out, - boolean inAttribute, boolean normaliseWhite, boolean stripLeadingWhite) throws IOException { + boolean inAttribute, boolean normaliseWhite, boolean stripLeadingWhite, boolean trimTrailing) throws IOException { boolean lastWasWhite = false; boolean reachedNonWhite = false; @@ -172,19 +172,28 @@ static void escape(Appendable accum, String string, OutputSettings out, final int length = string.length(); int codePoint; + boolean skipped = false; for (int offset = 0; offset < length; offset += Character.charCount(codePoint)) { codePoint = string.codePointAt(offset); if (normaliseWhite) { if (StringUtil.isWhitespace(codePoint)) { - if ((stripLeadingWhite && !reachedNonWhite) || lastWasWhite) + if (stripLeadingWhite && !reachedNonWhite) continue; + if (lastWasWhite) continue; + if (trimTrailing) { + skipped = true; continue; + } accum.append(' '); lastWasWhite = true; continue; } else { lastWasWhite = false; reachedNonWhite = true; + if (skipped) { + accum.append(' '); // wasn't the end, so need to place a normalized space + skipped = false; + } } } // surrogate pairs, split implementation for efficiency on single char common case (saves creating strings, char[]): diff --git a/src/main/java/org/jsoup/nodes/TextNode.java b/src/main/java/org/jsoup/nodes/TextNode.java index e32a0dbf13..4dbd116818 100644 --- a/src/main/java/org/jsoup/nodes/TextNode.java +++ b/src/main/java/org/jsoup/nodes/TextNode.java @@ -83,31 +83,27 @@ public TextNode splitText(int offset) { void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { final boolean prettyPrint = out.prettyPrint(); final Element parent = parentNode instanceof Element ? ((Element) parentNode) : null; - final boolean blank = isBlank(); final boolean normaliseWhite = prettyPrint && !Element.preserveWhitespace(parentNode); - // if this text is just whitespace, and the next node will cause an indent, skip this text: - if (normaliseWhite && blank) { - boolean canSkip = false; + boolean trimLeading = false; + boolean trimTrailing = false; + if (normaliseWhite) { + trimLeading = (siblingIndex == 0 && parent != null && parent.tag().isBlock()) || + parentNode instanceof Document; + trimTrailing = nextSibling() == null && parent != null && parent.tag().isBlock(); + + // if this text is just whitespace, and the next node will cause an indent, skip this text: Node next = this.nextSibling(); - if (next instanceof Element) { - Element nextEl = (Element) next; - canSkip = nextEl.shouldIndent(out); - } else if (next == null && parent != null) { // we are the last child, check parent - canSkip = parent.shouldIndent(out); - } else if (next instanceof TextNode && (((TextNode) next).isBlank())) { - // sometimes get a run of textnodes from parser if nodes are re-parented - canSkip = true; - } - if (canSkip) - return; - } + boolean couldSkip = (next instanceof Element && ((Element) next).shouldIndent(out)) // next will indent + || (next instanceof TextNode && (((TextNode) next).isBlank())); // next is blank text, from re-parenting + if (couldSkip && isBlank()) return; - if (prettyPrint && ((siblingIndex == 0 && parent != null && parent.tag().formatAsBlock() && !blank) || (out.outline() && siblingNodes().size() > 0 && !blank))) - indent(accum, depth, out); + if ((siblingIndex == 0 && parent != null && parent.tag().formatAsBlock() && !isBlank()) || + (out.outline() && siblingNodes().size() > 0 && !isBlank())) + indent(accum, depth, out); + } - final boolean stripWhite = prettyPrint && parentNode instanceof Document; - Entities.escape(accum, coreValue(), out, false, normaliseWhite, stripWhite); + Entities.escape(accum, coreValue(), out, false, normaliseWhite, trimLeading, trimTrailing); } void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} diff --git a/src/main/java/org/jsoup/nodes/XmlDeclaration.java b/src/main/java/org/jsoup/nodes/XmlDeclaration.java index 6ab9b16241..ca22eb7ff7 100644 --- a/src/main/java/org/jsoup/nodes/XmlDeclaration.java +++ b/src/main/java/org/jsoup/nodes/XmlDeclaration.java @@ -60,7 +60,7 @@ private void getWholeDeclaration(Appendable accum, Document.OutputSettings out) accum.append(key); if (!val.isEmpty()) { accum.append("=\""); - Entities.escape(accum, val, out, true, false, false); + Entities.escape(accum, val, out, true, false, false, false); accum.append('"'); } } diff --git a/src/test/java/org/jsoup/nodes/DocumentTest.java b/src/test/java/org/jsoup/nodes/DocumentTest.java index a490b8ea16..77a140aad7 100644 --- a/src/test/java/org/jsoup/nodes/DocumentTest.java +++ b/src/test/java/org/jsoup/nodes/DocumentTest.java @@ -55,15 +55,15 @@ public class DocumentTest { @Test public void testOutputEncoding() { Document doc = Jsoup.parse("<p title=π>π & < > </p>"); // default is utf-8 - assertEquals("<p title=\"π\">π &amp; &lt; &gt; </p>", doc.body().html()); + assertEquals("<p title=\"π\">π &amp; &lt; &gt;</p>", doc.body().html()); assertEquals("UTF-8", doc.outputSettings().charset().name()); doc.outputSettings().charset("ascii"); assertEquals(Entities.EscapeMode.base, doc.outputSettings().escapeMode()); - assertEquals("<p title=\"&#x3c0;\">&#x3c0; &amp; &lt; &gt; </p>", doc.body().html()); + assertEquals("<p title=\"&#x3c0;\">&#x3c0; &amp; &lt; &gt;</p>", doc.body().html()); doc.outputSettings().escapeMode(Entities.EscapeMode.extended); - assertEquals("<p title=\"&pi;\">&pi; &amp; &lt; &gt; </p>", doc.body().html()); + assertEquals("<p title=\"&pi;\">&pi; &amp; &lt; &gt;</p>", doc.body().html()); } @Test public void testXhtmlReferences() { diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index b2701e8441..1f92eb11d1 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -625,6 +626,7 @@ public void testAddNewText() { Document doc = Jsoup.parse("<div id=1><p>Hello</p></div>"); Element div = doc.getElementById("1"); div.appendText(" there & now >"); + assertEquals ("Hello there & now >", div.text()); assertEquals("<p>Hello</p> there &amp; now &gt;", TextUtil.stripNewlines(div.html())); } @@ -2305,4 +2307,31 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { assertEquals("<!--\n comment \n -->\n<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc5.html()); assertEquals("<!--\n comment \n -->\n<!doctype html>\n<html>\n <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n <body></body>\n</html>", doc6.html()); } + + @Test void textnodeInBlockIndent() { + String html ="<div>\n{{ msg }} \n </div>\n<div>\n{{ msg }} \n </div>"; + Document doc = Jsoup.parse(html); + assertEquals("<div>\n {{ msg }}\n</div>\n<div>\n {{ msg }}\n</div>", doc.body().html()); + } + + @Test void stripTrailing() { + String html = "<p> This <span>is </span>fine. </p>"; + Document doc = Jsoup.parse(html); + assertEquals("<p>This <span>is </span>fine.</p>", doc.body().html()); + } + + @Test void elementIndentAndSpaceTrims() { + String html = "<body><div> <p> One Two </p> <a> Hello </a><p>\nSome text \n</p>\n </div>"; + Document doc = Jsoup.parse(html); + assertEquals("<div>\n" + + " <p>One Two</p> <a> Hello </a>\n" + + " <p>Some text</p>\n" + + "</div>", doc.body().html()); + } + + @Test void divAInlineable() { + String html = "<body><div> <a>Text</a>"; + Document doc = Jsoup.parse(html); + assertEquals("<div><a>Text</a>\n</div>", doc.body().html()); + } } \ No newline at end of file diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java index dd1ecc36e4..48663a6371 100644 --- a/src/test/java/org/jsoup/parser/HtmlParserTest.java +++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java @@ -158,7 +158,7 @@ public class HtmlParserTest { @Test public void testSpaceAfterTag() { Document doc = Jsoup.parse("<div > <a name=\"top\"></a ><p id=1 >Hello</p></div>"); - assertEquals("<div> <a name=\"top\"></a><p id=\"1\">Hello</p></div>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<div><a name=\"top\"></a><p id=\"1\">Hello</p></div>", TextUtil.stripNewlines(doc.body().html())); } @Test public void createsDocumentStructure() { @@ -279,7 +279,7 @@ public class HtmlParserTest { @Test public void handlesWhatWgExpensesTableExample() { // http://www.whatwg.org/specs/web-apps/current-work/multipage/tabular-data.html#examples-0 Document doc = Jsoup.parse("<table> <colgroup> <col> <colgroup> <col> <col> <col> <thead> <tr> <th> <th>2008 <th>2007 <th>2006 <tbody> <tr> <th scope=rowgroup> Research and development <td> $ 1,109 <td> $ 782 <td> $ 712 <tr> <th scope=row> Percentage of net sales <td> 3.4% <td> 3.3% <td> 3.7% <tbody> <tr> <th scope=rowgroup> Selling, general, and administrative <td> $ 3,761 <td> $ 2,963 <td> $ 2,433 <tr> <th scope=row> Percentage of net sales <td> 11.6% <td> 12.3% <td> 12.6% </table>"); - assertEquals("<table><colgroup><col></colgroup><colgroup><col><col><col></colgroup><thead><tr><th></th><th>2008 </th><th>2007 </th><th>2006 </th></tr></thead><tbody><tr><th scope=\"rowgroup\"> Research and development </th><td> $ 1,109 </td><td> $ 782 </td><td> $ 712 </td></tr><tr><th scope=\"row\"> Percentage of net sales </th><td> 3.4% </td><td> 3.3% </td><td> 3.7% </td></tr></tbody><tbody><tr><th scope=\"rowgroup\"> Selling, general, and administrative </th><td> $ 3,761 </td><td> $ 2,963 </td><td> $ 2,433 </td></tr><tr><th scope=\"row\"> Percentage of net sales </th><td> 11.6% </td><td> 12.3% </td><td> 12.6% </td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<table><colgroup><col></colgroup><colgroup><col><col><col></colgroup><thead><tr><th></th><th>2008</th><th>2007</th><th>2006</th></tr></thead><tbody><tr><th scope=\"rowgroup\">Research and development</th><td>$ 1,109</td><td>$ 782</td><td>$ 712</td></tr><tr><th scope=\"row\">Percentage of net sales</th><td>3.4%</td><td>3.3%</td><td>3.7%</td></tr></tbody><tbody><tr><th scope=\"rowgroup\">Selling, general, and administrative</th><td>$ 3,761</td><td>$ 2,963</td><td>$ 2,433</td></tr><tr><th scope=\"row\">Percentage of net sales</th><td>11.6%</td><td>12.3%</td><td>12.6%</td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); } @Test public void handlesTbodyTable() { @@ -294,7 +294,7 @@ public class HtmlParserTest { @Test public void noTableDirectInTable() { Document doc = Jsoup.parse("<table> <td>One <td><table><td>Two</table> <table><td>Three"); - assertEquals("<table><tbody><tr><td>One </td><td><table><tbody><tr><td>Two</td></tr></tbody></table><table><tbody><tr><td>Three</td></tr></tbody></table></td></tr></tbody></table>", + assertEquals("<table><tbody><tr><td>One</td><td><table><tbody><tr><td>Two</td></tr></tbody></table><table><tbody><tr><td>Three</td></tr></tbody></table></td></tr></tbody></table>", TextUtil.stripNewlines(doc.body().html())); } @@ -568,7 +568,7 @@ public class HtmlParserTest { @Test public void normalisesDocument() { String h = "<!doctype html>One<html>Two<head>Three<link> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>Four<body>Five </body>Six </html>Seven "; Document doc = Jsoup.parse(h); - assertEquals("<!doctype html><html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>OneTwoThree<link>FourFive Six Seven </body></html>", + assertEquals("<!doctype html><html><head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body>OneTwoThree<link>FourFive Six Seven</body></html>", TextUtil.stripNewlines(doc.html())); } @@ -599,7 +599,7 @@ public class HtmlParserTest { @Test public void testHgroup() { // jsoup used to not allow hgroup in h{n}, but that's not in spec, and browsers are OK Document doc = Jsoup.parse("<h1>Hello <h2>There <hgroup><h1>Another<h2>headline</hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup>"); - assertEquals("<h1>Hello </h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup><hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<h1>Hello</h1><h2>There <hgroup><h1>Another</h1><h2>headline</h2></hgroup><hgroup><h1>More</h1><p>stuff</p></hgroup></h2>", TextUtil.stripNewlines(doc.body().html())); } @Test public void testRelaxedTags() { @@ -611,7 +611,7 @@ public class HtmlParserTest { // h* tags (h1 .. h9) in browsers can handle any internal content other than other h*. which is not per any // spec, which defines them as containing phrasing content only. so, reality over theory. Document doc = Jsoup.parse("<h1>Hello <div>There</div> now</h1> <h2>More <h3>Content</h3></h2>"); - assertEquals("<h1>Hello <div>There</div> now</h1><h2>More </h2><h3>Content</h3>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<h1>Hello <div>There</div> now</h1><h2>More</h2><h3>Content</h3>", TextUtil.stripNewlines(doc.body().html())); } @Test public void testSpanContents() { @@ -1175,7 +1175,8 @@ public void testInvalidTableContents() throws IOException { Document doc = Parser.htmlParser() .settings(preserveCase) .parseInput(html, ""); - assertEquals("<A>ONE </A><A>Two</A>", StringUtil.normaliseWhitespace(doc.body().html())); + //assertEquals("<A>ONE </A><A>Two</A>", StringUtil.normaliseWhitespace(doc.body().html())); + assertEquals("<A>ONE </A><A>Two</A>", doc.body().html()); } @Test public void normalizesDiscordantTags() { @@ -1246,7 +1247,7 @@ public void testInvalidTableContents() throws IOException { File in = ParseTest.getFile("/htmltests/comments.html"); Document doc = Jsoup.parse(in, "UTF-8"); - assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --> <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", + assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --> <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><!-- now --> <head><!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>", StringUtil.normaliseWhitespace(doc.html())); assertEquals("A Certain Kind of Test", doc.head().select("title").text()); @@ -1486,7 +1487,7 @@ private boolean didAddElements(String input) { String html = "<a>\n<b>\n<div>\n<a>test</a>\n</div>\n</b>\n</a>"; Document doc = Jsoup.parse(html); assertNotNull(doc); - assertEquals("<a> <b> </b></a><b><div><a></a><a>test</a></div> </b>", TextUtil.stripNewlines(doc.body().html())); + assertEquals("<a> <b> </b></a><b><div><a> </a><a>test</a></div> </b>", TextUtil.stripNewlines(doc.body().html())); } @Test public void tagsMustStartWithAscii() { diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java index c5dbbfc169..f05ee26897 100644 --- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java +++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java @@ -75,9 +75,9 @@ public void nestedAnchorElements01() { String s = Jsoup.parse(html).toString(); assertEquals("<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body> <a href=\"#1\"> </a>\n" + + " <body><a href=\"#1\"> </a>\n" + " <div>\n" + - " <a href=\"#1\"></a><a href=\"#2\">child</a>\n" + + " <a href=\"#1\"> </a><a href=\"#2\">child</a>\n" + " </div>\n" + " </body>\n" + "</html>", s); @@ -99,11 +99,11 @@ public void nestedAnchorElements02() { String s = Jsoup.parse(html).toString(); assertEquals("<html>\n" + " <head> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head>\n" + - " <body> <a href=\"#1\"> </a>\n" + + " <body><a href=\"#1\"> </a>\n" + " <div>\n" + - " <a href=\"#1\"></a>\n" + + " <a href=\"#1\"> </a>\n" + " <div>\n" + - " <a href=\"#1\"></a><a href=\"#2\">child</a>\n" + + " <a href=\"#1\"> </a><a href=\"#2\">child</a>\n" + " </div>\n" + " </div>\n" + " </body>\n" + diff --git a/src/test/java/org/jsoup/select/TraversorTest.java b/src/test/java/org/jsoup/select/TraversorTest.java index 8d0667e648..11a5167d61 100644 --- a/src/test/java/org/jsoup/select/TraversorTest.java +++ b/src/test/java/org/jsoup/select/TraversorTest.java @@ -95,7 +95,7 @@ public FilterResult tail(Node node, int depth) { return ("b".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE; } }, doc.select("div")); - assertEquals("<div></div>\n<div>\n There be \n</div>", doc.select("body").html()); + assertEquals("<div></div>\n<div>\n There be\n</div>", doc.select("body").html()); } @Test diff --git a/src/test/resources/htmltests/medium.html b/src/test/resources/htmltests/medium.html index 1f90df242e..728961cb52 100644 --- a/src/test/resources/htmltests/medium.html +++ b/src/test/resources/htmltests/medium.html @@ -1,10 +1,10 @@ <html> <head> - <title>Large HTML</title> + <title>Medium HTML</title> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head> <body> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero.</p> - <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p> + <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh.</p> <p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna.</p> <p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi.</p> <p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque.</p> From 38b32245201b405185c2c36c52637f8b27ddbdd2 Mon Sep 17 00:00:00 2001 From: jartysiewicz <jan.artysiewicz@gmail.com> Date: Fri, 24 Jun 2022 09:36:33 +0200 Subject: [PATCH 752/774] Correct javadoc and add @WillClose annotations Co-authored-by: Jan Artysiewicz <jan.artysiewicz@cronn.de> --- src/main/java/org/jsoup/Jsoup.java | 6 ++++-- src/main/java/org/jsoup/helper/DataUtil.java | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jsoup/Jsoup.java b/src/main/java/org/jsoup/Jsoup.java index 732fde7da8..dc86324b2a 100644 --- a/src/main/java/org/jsoup/Jsoup.java +++ b/src/main/java/org/jsoup/Jsoup.java @@ -8,6 +8,8 @@ import org.jsoup.safety.Safelist; import javax.annotation.Nullable; +import javax.annotation.WillClose; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -185,7 +187,7 @@ public static Document parse(File file, @Nullable String charsetName, String bas /** Read an input stream, and parse it to a Document. - @param in input stream to read. Make sure to close it after parsing. + @param in input stream to read. The stream will be closed after reading. @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if present, or fall back to {@code UTF-8} (which is often safe to do). @param baseUri The URL where the HTML was retrieved from, to resolve relative links against. @@ -193,7 +195,7 @@ public static Document parse(File file, @Nullable String charsetName, String bas @throws IOException if the file could not be found, or read, or if the charsetName is invalid. */ - public static Document parse(InputStream in, @Nullable String charsetName, String baseUri) throws IOException { + public static Document parse(@WillClose InputStream in, @Nullable String charsetName, String baseUri) throws IOException { return DataUtil.load(in, charsetName, baseUri); } diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index 7100f52c58..f7868874b1 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -13,6 +13,8 @@ import org.jsoup.select.Elements; import javax.annotation.Nullable; +import javax.annotation.WillClose; + import java.io.BufferedReader; import java.io.CharArrayReader; import java.io.File; @@ -103,7 +105,7 @@ public static Document load(File file, @Nullable String charsetName, String base * @return Document * @throws IOException on IO error */ - public static Document load(InputStream in, @Nullable String charsetName, String baseUri) throws IOException { + public static Document load(@WillClose InputStream in, @Nullable String charsetName, String baseUri) throws IOException { return parseInputStream(in, charsetName, baseUri, Parser.htmlParser()); } @@ -116,7 +118,7 @@ public static Document load(InputStream in, @Nullable String charsetName, String * @return Document * @throws IOException on IO error */ - public static Document load(InputStream in, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { + public static Document load(@WillClose InputStream in, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { return parseInputStream(in, charsetName, baseUri, parser); } @@ -134,7 +136,7 @@ static void crossStreams(final InputStream in, final OutputStream out) throws IO } } - static Document parseInputStream(@Nullable InputStream input, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { + static Document parseInputStream(@Nullable @WillClose InputStream input, @Nullable String charsetName, String baseUri, Parser parser) throws IOException { if (input == null) // empty body return new Document(baseUri); input = ConstrainableInputStream.wrap(input, bufferSize, 0); From b873e21ea050b5f5c52b1d5b60bbe204dab22e99 Mon Sep 17 00:00:00 2001 From: Jeremy Landis <jeremylandis@hotmail.com> Date: Fri, 24 Jun 2022 04:11:23 -0400 Subject: [PATCH 753/774] Use Charset.forname, to better cache charset lookups --- src/main/java/org/jsoup/helper/DataUtil.java | 2 +- src/main/java/org/jsoup/helper/HttpConnection.java | 2 +- src/test/java/org/jsoup/helper/DataUtilTest.java | 12 ++++-------- src/test/java/org/jsoup/parser/ParserTest.java | 3 ++- src/test/java/org/jsoup/parser/TokeniserTest.java | 6 +++--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/jsoup/helper/DataUtil.java b/src/main/java/org/jsoup/helper/DataUtil.java index f7868874b1..3f34450b7f 100644 --- a/src/main/java/org/jsoup/helper/DataUtil.java +++ b/src/main/java/org/jsoup/helper/DataUtil.java @@ -208,7 +208,7 @@ else if (first instanceof Comment) { if (doc == null) { if (charsetName == null) charsetName = defaultCharsetName; - BufferedReader reader = new BufferedReader(new InputStreamReader(input, charsetName), bufferSize); // Android level does not allow us try-with-resources + BufferedReader reader = new BufferedReader(new InputStreamReader(input, Charset.forName(charsetName)), bufferSize); // Android level does not allow us try-with-resources try { if (bomCharset != null && bomCharset.offset) { // creating the buffered reader ignores the input pos, so must skip here long skipped = reader.skip(1); diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 3ba82b4c2b..b99807302d 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -1163,7 +1163,7 @@ else if (needsMultipart(req)) { private static void writePost(final Connection.Request req, final OutputStream outputStream, @Nullable final String boundary) throws IOException { final Collection<Connection.KeyVal> data = req.data(); - final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(outputStream, req.postDataCharset())); + final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(outputStream, Charset.forName(req.postDataCharset()))); if (boundary != null) { // boundary will be set if we're in multipart mode diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index 59027aeb52..a57ad41687 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import java.io.*; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -37,12 +38,7 @@ private InputStream stream(String data) { } private InputStream stream(String data, String charset) { - try { - return new ByteArrayInputStream(data.getBytes(charset)); - } catch (UnsupportedEncodingException e) { - fail(); - } - return null; + return new ByteArrayInputStream(data.getBytes(Charset.forName(charset))); } @Test @@ -180,7 +176,7 @@ public void supportsUTF8BOM() throws IOException { @Test public void noExtraNULLBytes() throws IOException { - final byte[] b = "<html><head><meta charset=\"UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div><u>ü</u>ü</div></body></html>".getBytes("UTF-8"); + final byte[] b = "<html><head><meta charset=\"UTF-8\"> <style> .commit-tease, .user-profile-mini-avatar, .avatar, .vcard-details, .signup-prompt-bg { display: none !IMPORTANT; } </style> <script> document.addEventListener('DOMContentLoaded', function() { this.querySelectorAll('a').forEach(anchor => { anchor.addEventListener('click', e => { e.preventDefault(); const redact = new URLSearchParams(window.location.search).get('redact'); const hasExistingParams = anchor.href.includes('?'); window.location.href = anchor.href + (hasExistingParams ? `&redact=${redact}` : `?redact=${redact}`); }); }); }); </script> </head><body><div><u>ü</u>ü</div></body></html>".getBytes(StandardCharsets.UTF_8); Document doc = Jsoup.parse(new ByteArrayInputStream(b), null, ""); assertFalse( doc.outerHtml().contains("\u0000") ); @@ -201,7 +197,7 @@ public void supportsXmlCharsetDeclaration() throws IOException { "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">Hellö Wörld!</html>" - ).getBytes(encoding)); + ).getBytes(Charset.forName(encoding))); Document doc = Jsoup.parse(soup, null, ""); assertEquals("Hellö Wörld!", doc.body().text()); diff --git a/src/test/java/org/jsoup/parser/ParserTest.java b/src/test/java/org/jsoup/parser/ParserTest.java index 7a97f2c680..d16ed65df2 100644 --- a/src/test/java/org/jsoup/parser/ParserTest.java +++ b/src/test/java/org/jsoup/parser/ParserTest.java @@ -6,6 +6,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,7 +32,7 @@ public void unescapeEntitiesHandlesLargeInput() { @Test public void testUtf8() throws IOException { // testcase for https://github.com/jhy/jsoup/issues/1557. no repro. - Document parsed = Jsoup.parse(new ByteArrayInputStream("<p>H\u00E9llo, w\u00F6rld!".getBytes("UTF-8")), null, ""); + Document parsed = Jsoup.parse(new ByteArrayInputStream("<p>H\u00E9llo, w\u00F6rld!".getBytes(StandardCharsets.UTF_8)), null, ""); String text = parsed.selectFirst("p").wholeText(); assertEquals(text, "H\u00E9llo, w\u00F6rld!"); } diff --git a/src/test/java/org/jsoup/parser/TokeniserTest.java b/src/test/java/org/jsoup/parser/TokeniserTest.java index d7a6b10936..bfdbb8eefc 100644 --- a/src/test/java/org/jsoup/parser/TokeniserTest.java +++ b/src/test/java/org/jsoup/parser/TokeniserTest.java @@ -5,7 +5,7 @@ import org.jsoup.select.Elements; import org.junit.jupiter.api.Test; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.util.Arrays; import static org.jsoup.parser.CharacterReader.maxBufferLen; @@ -165,9 +165,9 @@ public void bufferUpInAttributeVal() { assertEquals(1, parser.getErrors().size()); } - @Test public void cp1252SubstitutionTable() throws UnsupportedEncodingException { + @Test public void cp1252SubstitutionTable() { for (int i = 0; i < Tokeniser.win1252Extensions.length; i++) { - String s = new String(new byte[]{ (byte) (i + Tokeniser.win1252ExtensionsStart) }, "Windows-1252"); + String s = new String(new byte[]{ (byte) (i + Tokeniser.win1252ExtensionsStart) }, Charset.forName("Windows-1252")); assertEquals(1, s.length()); // some of these characters are illegal From 2b573de884aad36f3f6be3dc46e51ee06b7269f1 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 3 Jul 2022 17:20:53 +1000 Subject: [PATCH 754/774] Disable namespaces in Element#selectXpath Simplifies use of xpath queries when there are xmlns attributes set, by allowing elements to be found by their local name, consistently. Fixes #180 --- CHANGES | 4 ++ src/main/java/org/jsoup/helper/W3CDom.java | 27 +++++++++-- src/main/java/org/jsoup/nodes/Element.java | 2 + src/main/java/org/jsoup/nodes/NodeUtils.java | 2 +- .../java/org/jsoup/helper/W3CDomTest.java | 14 ++++++ src/test/java/org/jsoup/select/XpathTest.java | 45 ++++++++++++++++--- 6 files changed, 84 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 33ed77eb49..55b406af03 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,10 @@ jsoup changelog so that they are indented correctly. <https://github.com/jhy/jsoup/issues/1798> + * Improvement: in Element#selectXpath(), disable namespace awareness. This makes it possible to always select elements + by their simple local name, regardless of whether an xmlns attribute was set. + <https://github.com/jhy/jsoup/issues/1801> + * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default internal buffer size is read. diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 753c91265f..76ff4fb058 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -52,7 +52,6 @@ public class W3CDom { private static final String ContextProperty = "jsoupContextSource"; // tracks the jsoup context element on w3c doc private static final String ContextNodeProperty = "jsoupContextNode"; // the w3c node used as the creating context - /** To get support for XPath versions &gt; 1, set this property to the classname of an alternate XPathFactory implementation. (For e.g. {@code net.sf.saxon.xpath.XPathFactoryImpl}). @@ -60,12 +59,33 @@ public class W3CDom { public static final String XPathFactoryProperty = "javax.xml.xpath.XPathFactory:jsoup"; protected DocumentBuilderFactory factory; + private boolean namespaceAware = true; // false when using selectXpath, for user's query convenience public W3CDom() { factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); } + /** + Returns if this W3C DOM is namespace aware. By default, this will be {@code true}, but is disabled for simplicity + when using XPath selectors in {@link org.jsoup.nodes.Element#selectXpath(String)}. + @return the current namespace aware setting. + */ + public boolean namespaceAware() { + return namespaceAware; + } + + /** + Update the namespace aware setting. This impacts the factory that is used to create W3C nodes from jsoup nodes. + @param namespaceAware the updated setting + @return this W3CDom, for chaining. + */ + public W3CDom namespaceAware(boolean namespaceAware) { + this.namespaceAware = namespaceAware; + factory.setNamespaceAware(namespaceAware); + return this; + } + /** * Converts a jsoup DOM to a W3C DOM. * @@ -92,7 +112,6 @@ public static Document convert(org.jsoup.nodes.Document in) { * @see OutputKeys#STANDALONE * @see OutputKeys#STANDALONE * @see OutputKeys#DOCTYPE_PUBLIC - * @see OutputKeys#DOCTYPE_PUBLIC * @see OutputKeys#CDATA_SECTION_ELEMENTS * @see OutputKeys#INDENT * @see OutputKeys#MEDIA_TYPE @@ -314,7 +333,7 @@ public String asString(Document doc) { /** * Implements the conversion by walking the input. */ - protected static class W3CBuilder implements NodeVisitor { + protected class W3CBuilder implements NodeVisitor { private static final String xmlnsKey = "xmlns"; private static final String xmlnsPrefix = "xmlns:"; @@ -337,7 +356,7 @@ public void head(org.jsoup.nodes.Node source, int depth) { org.jsoup.nodes.Element sourceEl = (org.jsoup.nodes.Element) source; String prefix = updateNamespaces(sourceEl); - String namespace = namespacesStack.peek().get(prefix); + String namespace = namespaceAware ? namespacesStack.peek().get(prefix) : null; String tagName = sourceEl.tagName(); /* Tag names in XML are quite permissive, but less permissive than HTML. Rather than reimplement the validation, diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index e1ab957e71..948c6fd2c8 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -522,6 +522,8 @@ public boolean is(Evaluator evaluator) { /** Find Elements that match the supplied XPath expression. + <p>Note that for convenience of writing the Xpath expression, namespaces are disabled, and queries can be + expressed using the elements local name only.</p> <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an alternate XPathFactory implementation:</p> <ol> diff --git a/src/main/java/org/jsoup/nodes/NodeUtils.java b/src/main/java/org/jsoup/nodes/NodeUtils.java index 722a614f1b..e45f5c532f 100644 --- a/src/main/java/org/jsoup/nodes/NodeUtils.java +++ b/src/main/java/org/jsoup/nodes/NodeUtils.java @@ -42,7 +42,7 @@ static <T extends Node> List<T> selectXpath(String xpath, Element el, Class<T> n Validate.notNull(el); Validate.notNull(nodeType); - W3CDom w3c = new W3CDom(); + W3CDom w3c = new W3CDom().namespaceAware(false); org.w3c.dom.Document wDoc = w3c.fromJsoup(el); org.w3c.dom.Node contextNode = w3c.contextNode(wDoc); NodeList nodeList = w3c.selectXpath(xpath, contextNode); diff --git a/src/test/java/org/jsoup/helper/W3CDomTest.java b/src/test/java/org/jsoup/helper/W3CDomTest.java index 09fc66f352..205737280d 100644 --- a/src/test/java/org/jsoup/helper/W3CDomTest.java +++ b/src/test/java/org/jsoup/helper/W3CDomTest.java @@ -278,6 +278,20 @@ public void xmlnsXpathTest() throws XPathExpressionException { assertNull(nodeList); } + @Test + void canDisableNamespaces() throws XPathExpressionException { + W3CDom w3c = new W3CDom(); + assertTrue(w3c.namespaceAware()); + + w3c.namespaceAware(false); + assertFalse(w3c.namespaceAware()); + + String html = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>"; + Document dom = w3c.fromJsoup(Jsoup.parse(html)); + NodeList nodeList = xpath(dom, "//body");// no ns, so needs no prefix + assertEquals("div", nodeList.item(0).getLocalName()); + } + private NodeList xpath(Document w3cDoc, String query) throws XPathExpressionException { XPathExpression xpath = XPathFactory.newInstance().newXPath().compile(query); return ((NodeList) xpath.evaluate(w3cDoc, XPathConstants.NODE)); diff --git a/src/test/java/org/jsoup/select/XpathTest.java b/src/test/java/org/jsoup/select/XpathTest.java index 2b3393a2ce..274800eecc 100644 --- a/src/test/java/org/jsoup/select/XpathTest.java +++ b/src/test/java/org/jsoup/select/XpathTest.java @@ -1,7 +1,6 @@ package org.jsoup.select; import org.jsoup.Jsoup; -import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; @@ -17,7 +16,6 @@ import javax.xml.xpath.XPathFactoryConfigurationException; import javax.xml.xpath.XPathFunctionResolver; import javax.xml.xpath.XPathVariableResolver; - import java.util.List; import java.util.stream.Stream; @@ -76,8 +74,8 @@ public void throwsSelectException() { } @Test - public void supportsNamespaces() { - String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>";; + public void supportsLocalname() { + String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>"; Document doc = Jsoup.parse(xhtml, Parser.xmlParser()); Elements elements = doc.selectXpath("//*[local-name()='body']"); assertEquals(1, elements.size()); @@ -86,7 +84,7 @@ public void supportsNamespaces() { @Test public void canDitchNamespaces() { - String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>";; + String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>"; Document doc = Jsoup.parse(xhtml, Parser.xmlParser()); doc.select("[xmlns]").removeAttr("xmlns"); Elements elements = doc.selectXpath("//*[local-name()='body']"); @@ -192,8 +190,45 @@ public void canSupplyAlternateFactoryImpl() { } assertTrue(threw); System.clearProperty(XPathFactoryProperty); + } + + @Test + public void notNamespaceAware() { + String xhtml = "<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>"; + Document doc = Jsoup.parse(xhtml, Parser.xmlParser()); + Elements elements = doc.selectXpath("//body"); + assertEquals(1, elements.size()); + assertEquals("One", elements.first().id()); + } + + @Test + public void supportsPrefixes() { + // example from https://www.w3.org/TR/xml-names/ + String xml = "<?xml version=\"1.0\"?>\n" + + "<bk:book xmlns:bk='urn:loc.gov:books'\n" + + " xmlns:isbn='urn:ISBN:0-395-36341-6'>\n" + + " <bk:title>Cheaper by the Dozen</bk:title>\n" + + " <isbn:number>1568491379</isbn:number>\n" + + "</bk:book>"; + Document doc = Jsoup.parse(xml, Parser.xmlParser()); + + //Elements elements = doc.selectXpath("//bk:book/bk:title"); + Elements elements = doc.selectXpath("//book/title"); + assertEquals(1, elements.size()); + assertEquals("Cheaper by the Dozen", elements.first().text()); + + // with prefix + Elements byPrefix = doc.selectXpath("//*[name()='bk:book']/*[name()='bk:title']"); + assertEquals(1, byPrefix.size()); + assertEquals("Cheaper by the Dozen", byPrefix.first().text()); + Elements byLocalName = doc.selectXpath("//*[local-name()='book']/*[local-name()='title']"); + assertEquals(1, byLocalName.size()); + assertEquals("Cheaper by the Dozen", byLocalName.first().text()); + Elements isbn = doc.selectXpath("//book/number"); + assertEquals(1, isbn.size()); + assertEquals("1568491379", isbn.first().text()); } // minimal, no-op implementation class to verify users can load a factory to support XPath 2.0 etc From 7fb6d02a43c25ff60f992a702a5baf47c01f6b5b Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 3 Jul 2022 17:28:56 +1000 Subject: [PATCH 755/774] Keep the W3CBuilder static Ensures API backcompat --- src/main/java/org/jsoup/helper/W3CDom.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index 76ff4fb058..f300b400c1 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -240,6 +240,7 @@ public void convert(org.jsoup.nodes.Document in, Document out) { */ public void convert(org.jsoup.nodes.Element in, Document out) { W3CBuilder builder = new W3CBuilder(out); + builder.namespaceAware = namespaceAware; org.jsoup.nodes.Document inDoc = in.ownerDocument(); if (inDoc != null) { if (!StringUtil.isBlank(inDoc.location())) { @@ -333,11 +334,12 @@ public String asString(Document doc) { /** * Implements the conversion by walking the input. */ - protected class W3CBuilder implements NodeVisitor { + protected static class W3CBuilder implements NodeVisitor { private static final String xmlnsKey = "xmlns"; private static final String xmlnsPrefix = "xmlns:"; private final Document doc; + private boolean namespaceAware = true; private final Stack<HashMap<String, String>> namespacesStack = new Stack<>(); // stack of namespaces, prefix => urn private Node dest; private Syntax syntax = Syntax.xml; // the syntax (to coerce attributes to). From the input doc if available. From 1541765f3efe10541452e048cd22e33e35467528 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 4 Jul 2022 16:36:53 +1000 Subject: [PATCH 756/774] Javadoc tweak --- src/main/java/org/jsoup/nodes/Element.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 948c6fd2c8..016dac13bd 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -465,6 +465,7 @@ public Elements select(Evaluator evaluator) { @param cssQuery a {@link Selector} CSS-like query @return the first matching element @throws IllegalArgumentException if no match is found + @since 1.15.2 */ public Element expectFirst(String cssQuery) { return (Element) Validate.ensureNotNull(Selector.selectFirst(cssQuery, this)); @@ -523,7 +524,7 @@ public boolean is(Evaluator evaluator) { /** Find Elements that match the supplied XPath expression. <p>Note that for convenience of writing the Xpath expression, namespaces are disabled, and queries can be - expressed using the elements local name only.</p> + expressed using the element's local name only.</p> <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an alternate XPathFactory implementation:</p> <ol> From d9566b5340487cd319660b553af11b60f5c8060a Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 4 Jul 2022 16:40:33 +1000 Subject: [PATCH 757/774] [maven-release-plugin] prepare release jsoup-1.15.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ba9f783565..0c3155803b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.15.2-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> + <version>1.15.2</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.15.2</tag> </scm> <organization> <name>Jonathan Hedley</name> From 1ac18083b0a0d90f40a104663d9754d2921289dc Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 4 Jul 2022 16:40:36 +1000 Subject: [PATCH 758/774] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0c3155803b..670465f13e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.15.2</version><!-- remember to update previous version below for japicmp --> + <version>1.15.3-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>jsoup-1.15.2</tag> + <tag>HEAD</tag> </scm> <organization> <name>Jonathan Hedley</name> From 4cfcaf7165b239c7fa475dd9ceebbf2fc3f77726 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Mon, 4 Jul 2022 16:55:05 +1000 Subject: [PATCH 759/774] 1.15.2 release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 55b406af03..2b2001127a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ jsoup changelog -*** Release 1.15.2 [PENDING] +*** Release 1.15.2 [2022-Jul-04] * Improvement: added the ability to track the position (line, column, index) in the original input source from where a given node was parsed. Accessible via Node.sourceRange() and Element.endSourceRange(). <https://github.com/jhy/jsoup/pull/1790> From 72680c4263ab8ed67df114805a43035270a24c51 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 6 Jul 2022 14:34:12 +1000 Subject: [PATCH 760/774] Bump jetty to 9.4.48.v20220622 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 670465f13e..0052fe9ef1 100644 --- a/pom.xml +++ b/pom.xml @@ -333,7 +333,7 @@ <!-- jetty for webserver integration tests. 9.x is last with Java7 support --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <version>9.4.46.v20220331</version> + <version>9.4.48.v20220622</version> <scope>test</scope> </dependency> @@ -341,7 +341,7 @@ <!-- jetty for webserver integration tests --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <version>9.4.46.v20220331</version> + <version>9.4.48.v20220622</version> <scope>test</scope> </dependency> From 12175ab43e639289c387d4af4a3741a477416f45 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 6 Jul 2022 16:07:12 +1000 Subject: [PATCH 761/774] Track source position from original to cleaned elements. --- CHANGES | 4 ++++ src/main/java/org/jsoup/safety/Cleaner.java | 7 +++++++ src/test/java/org/jsoup/safety/CleanerTest.java | 16 ++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGES b/CHANGES index 2b2001127a..4c7c671ff7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ jsoup changelog +Release 1.15.3 [PENDING] + * Improvement: the Cleaner will preserve the source position of cleaned elements, if source tracking is enabled in the + original parse. + *** Release 1.15.2 [2022-Jul-04] * Improvement: added the ability to track the position (line, column, index) in the original input source from where a given node was parsed. Accessible via Node.sourceRange() and Element.endSourceRange(). diff --git a/src/main/java/org/jsoup/safety/Cleaner.java b/src/main/java/org/jsoup/safety/Cleaner.java index 8d599ce88f..5eeeceb21b 100644 --- a/src/main/java/org/jsoup/safety/Cleaner.java +++ b/src/main/java/org/jsoup/safety/Cleaner.java @@ -160,6 +160,13 @@ private ElementMeta createSafeElement(Element sourceEl) { Attributes enforcedAttrs = safelist.getEnforcedAttributes(sourceTag); destAttrs.addAll(enforcedAttrs); + // Copy the original start and end range, if set + // TODO - might be good to make a generic Element#userData set type interface, and copy those all over + if (sourceEl.sourceRange().isTracked()) + sourceEl.sourceRange().track(dest, true); + if (sourceEl.endSourceRange().isTracked()) + sourceEl.endSourceRange().track(dest, false); + return new ElementMeta(dest, numDiscarded); } diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 8ae21d2019..3fc6dbba05 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -4,7 +4,10 @@ import org.jsoup.MultiLocaleExtension.MultiLocaleTest; import org.jsoup.TextUtil; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.jsoup.nodes.Entities; +import org.jsoup.nodes.Range; +import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; import java.util.Locale; @@ -339,4 +342,17 @@ public void bailsIfRemovingProtocolThatsNotSet() { assertEquals(Document.OutputSettings.Syntax.xml, result.outputSettings().syntax()); assertEquals("<p>test<br /></p>", result.body().html()); } + + @Test void preservesSourcePositionViaUserData() { + Document orig = Jsoup.parse("<script>xss</script>\n <p>Hello</p>", Parser.htmlParser().setTrackPosition(true)); + Element p = orig.expectFirst("p"); + Range origRange = p.sourceRange(); + assertEquals("2,2:22-2,5:25", origRange.toString()); + + Document clean = new Cleaner(Safelist.relaxed()).clean(orig); + Element cleanP = clean.expectFirst("p"); + Range cleanRange = cleanP.sourceRange(); + assertEquals(cleanRange, origRange); + assertEquals(clean.endSourceRange(), orig.endSourceRange()); + } } From a486f4e25ce2e5a4f406fa3cc798ae1cf2084d7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 13:02:27 +1000 Subject: [PATCH 762/774] Bump maven-bundle-plugin from 5.1.6 to 5.1.8 (#1820) Bumps maven-bundle-plugin from 5.1.6 to 5.1.8. --- updated-dependencies: - dependency-name: org.apache.felix:maven-bundle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0052fe9ef1..fd0393e944 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> - <version>5.1.6</version> + <version>5.1.8</version> <executions> <execution> <id>bundle-manifest</id> From 57d77cab959ec397521260df034e5f26e8e4e44a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 13:03:13 +1000 Subject: [PATCH 763/774] Bump gson from 2.9.0 to 2.9.1 (#1818) Bumps [gson](https://github.com/google/gson) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.9.0...gson-parent-2.9.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fd0393e944..24d0df636e 100644 --- a/pom.xml +++ b/pom.xml @@ -325,7 +325,7 @@ <!-- gson, to fetch entities from w3.org --> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>2.9.0</version> + <version>2.9.1</version> <scope>test</scope> </dependency> From 257ee54fbc452e6b23bcafc03691cf0efbec4e12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 13:03:36 +1000 Subject: [PATCH 764/774] Bump junit-jupiter from 5.8.2 to 5.9.0 (#1819) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.8.2 to 5.9.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.8.2...r5.9.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24d0df636e..b78d23df03 100644 --- a/pom.xml +++ b/pom.xml @@ -317,7 +317,7 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> - <version>5.8.2</version> + <version>5.9.0</version> <scope>test</scope> </dependency> From 5b193902029a453b95cfc8850d17e630079cf0a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 13:07:14 +1000 Subject: [PATCH 765/774] Bump maven-resources-plugin from 3.2.0 to 3.3.0 (#1814) Bumps [maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/apache/maven-resources-plugin/releases) - [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.2.0...maven-resources-plugin-3.3.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-resources-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b78d23df03..37f9e2ebb7 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> - <version>3.2.0</version> + <version>3.3.0</version> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> From fa13c8066b13f27a92d0057d55bf64e6d66ddff2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 7 Aug 2022 13:22:05 +1000 Subject: [PATCH 766/774] Added jar manifest default implementation entries. Implements #1809 --- CHANGES | 3 +++ pom.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 4c7c671ff7..9b2b03653b 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ Release 1.15.3 [PENDING] * Improvement: the Cleaner will preserve the source position of cleaned elements, if source tracking is enabled in the original parse. + * Build Improvement: added implementation version and related fields to the jar manifest. + <https://github.com/jhy/jsoup/issues/1809> + *** Release 1.15.2 [2022-Jul-04] * Improvement: added the ability to track the position (line, column, index) in the original input source from where a given node was parsed. Accessible via Node.sourceRange() and Element.endSourceRange(). diff --git a/pom.xml b/pom.xml index 37f9e2ebb7..9650d72c0c 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,9 @@ <version>3.2.2</version> <configuration> <archive> + <manifest> + <addDefaultImplementationEntries>true</addDefaultImplementationEntries> + </manifest> <manifestEntries> <Automatic-Module-Name>org.jsoup</Automatic-Module-Name> </manifestEntries> From c58112a2eddd630a4f6d76450034c1227ef5f842 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 7 Aug 2022 15:51:18 +1000 Subject: [PATCH 767/774] Set the read size correctly when capped The read size of the inputstream should be the desired remaining max (if set), but no larger than the defined buffer size. Fixes #1807 See #1774, 1671 --- CHANGES | 4 ++ .../internal/ConstrainableInputStream.java | 8 ++-- .../java/org/jsoup/helper/DataUtilTest.java | 47 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9b2b03653b..63a5861dd3 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ Release 1.15.3 [PENDING] * Improvement: the Cleaner will preserve the source position of cleaned elements, if source tracking is enabled in the original parse. + * Bugfix: the DataUtil would incorrectly read from InputStreams that emitted reads less than the requested size. This + lead to incorrect results when parsing from chunked server responses, for e.g. + <https://github.com/jhy/jsoup/issues/1807> + * Build Improvement: added implementation version and related fields to the jar manifest. <https://github.com/jhy/jsoup/issues/1809> diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 5b6491363e..54928f4e49 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -81,14 +81,16 @@ public ByteBuffer readToByteBuffer(int max) throws IOException { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize); int read; + int remaining = max; while (true) { - read = read(readBuffer, 0, bufferSize); + read = read(readBuffer, 0, localCapped ? Math.min(remaining, bufferSize) : bufferSize); if (read == -1) break; if (localCapped) { // this local byteBuffer cap may be smaller than the overall maxSize (like when reading first bytes) - if (read >= max) { - outStream.write(readBuffer, 0, max); + if (read >= remaining) { + outStream.write(readBuffer, 0, remaining); break; } + remaining -= read; } outStream.write(readBuffer, 0, read); } diff --git a/src/test/java/org/jsoup/helper/DataUtilTest.java b/src/test/java/org/jsoup/helper/DataUtilTest.java index a57ad41687..10074d4ca9 100644 --- a/src/test/java/org/jsoup/helper/DataUtilTest.java +++ b/src/test/java/org/jsoup/helper/DataUtilTest.java @@ -1,11 +1,13 @@ package org.jsoup.helper; import org.jsoup.Jsoup; +import org.jsoup.integration.ParseTest; import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.junit.jupiter.api.Test; import java.io.*; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -228,4 +230,49 @@ public void handlesFakeGzipFile() throws IOException { assertEquals("This is not gzipped", doc.title()); assertEquals("And should still be readable.", doc.selectFirst("p").text()); } + + // an input stream to give a range of output sizes, that changes on each read + static class VaryingReadInputStream extends InputStream { + final InputStream in; + int stride = 0; + + VaryingReadInputStream(InputStream in) { + this.in = in; + } + + public int read() throws IOException { + return in.read(); + } + + public int read(byte[] b) throws IOException { + return in.read(b, 0, Math.min(b.length, ++stride)); + } + + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, Math.min(len, ++stride)); + } + } + + @Test + void handlesChunkedInputStream() throws IOException { + File inputFile = ParseTest.getFile("/htmltests/large.html"); + String input = ParseTest.getFileAsString(inputFile); + VaryingReadInputStream stream = new VaryingReadInputStream(ParseTest.inputStreamFrom(input)); + + Document expected = Jsoup.parse(input, "https://example.com"); + Document doc = Jsoup.parse(stream, null, "https://example.com"); + assertTrue(doc.hasSameValue(expected)); + } + + @Test + void handlesUnlimitedRead() throws IOException { + File inputFile = ParseTest.getFile("/htmltests/large.html"); + String input = ParseTest.getFileAsString(inputFile); + VaryingReadInputStream stream = new VaryingReadInputStream(ParseTest.inputStreamFrom(input)); + + ByteBuffer byteBuffer = DataUtil.readToByteBuffer(stream, 0); + String read = new String(byteBuffer.array()); + + assertEquals(input, read); + } } From 5ed84f631e8889abc9b20ebcc898d053d77bb05d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Tue, 9 Aug 2022 12:56:27 +1000 Subject: [PATCH 768/774] Simplified the Test Server startup Removed the counting latch as Jetty will only start if it's not already running, so the latch was redundant. The stop wasn't being called anyway (which is good - we want the same server to run throughout the test session, vs restarting for each test class). Also ensure the server binds only to localhost, vs to 0.0.0.0. Fixes #1822 --- .../org/jsoup/integration/ConnectTest.java | 6 ---- .../java/org/jsoup/integration/SessionIT.java | 7 ----- .../org/jsoup/integration/SessionTest.java | 6 ---- .../org/jsoup/integration/TestServer.java | 29 ++++--------------- .../java/org/jsoup/nodes/FormElementTest.java | 6 ---- .../java/org/jsoup/nodes/PositionTest.java | 14 --------- 6 files changed, 6 insertions(+), 62 deletions(-) diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index e18a0f1a0e..23b89dd044 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -12,7 +12,6 @@ import org.jsoup.parser.HtmlTreeBuilder; import org.jsoup.parser.Parser; import org.jsoup.parser.XmlTreeBuilder; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -41,11 +40,6 @@ public static void setUp() { echoUrl = EchoServlet.Url; } - @AfterAll - public static void tearDown() { - TestServer.stop(); - } - @Test public void canConnectToLocalServer() throws IOException { String url = HelloServlet.Url; diff --git a/src/test/java/org/jsoup/integration/SessionIT.java b/src/test/java/org/jsoup/integration/SessionIT.java index 29fff08013..215422a1a2 100644 --- a/src/test/java/org/jsoup/integration/SessionIT.java +++ b/src/test/java/org/jsoup/integration/SessionIT.java @@ -3,11 +3,9 @@ import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.UncheckedIOException; -import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.FileServlet; import org.jsoup.integration.servlets.SlowRider; import org.jsoup.nodes.Document; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -23,11 +21,6 @@ public static void setUp() { TestServer.start(); } - @AfterAll - public static void tearDown() { - TestServer.stop(); - } - @Test public void multiThread() throws InterruptedException { int numThreads = 20; diff --git a/src/test/java/org/jsoup/integration/SessionTest.java b/src/test/java/org/jsoup/integration/SessionTest.java index 1de2e0cead..89264050f2 100644 --- a/src/test/java/org/jsoup/integration/SessionTest.java +++ b/src/test/java/org/jsoup/integration/SessionTest.java @@ -8,7 +8,6 @@ import org.jsoup.nodes.Document; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -24,11 +23,6 @@ public static void setUp() { TestServer.start(); } - @AfterAll - public static void tearDown() { - TestServer.stop(); - } - private static Elements keyEls(String key, Document doc) { return doc.select("th:contains(" + key + ") + td"); } diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java index 6af7c21a10..fa370d1524 100644 --- a/src/test/java/org/jsoup/integration/TestServer.java +++ b/src/test/java/org/jsoup/integration/TestServer.java @@ -5,12 +5,11 @@ import org.eclipse.jetty.servlet.ServletHandler; import org.jsoup.integration.servlets.BaseServlet; -import java.util.concurrent.atomic.AtomicInteger; +import java.net.InetSocketAddress; public class TestServer { - private static final Server jetty = new Server(0); + private static final Server jetty = new Server(new InetSocketAddress("localhost", 0)); private static final ServletHandler handler = new ServletHandler(); - private static AtomicInteger latch = new AtomicInteger(0); static { jetty.setHandler(handler); @@ -21,26 +20,10 @@ private TestServer() { public static void start() { synchronized (jetty) { - int count = latch.getAndIncrement(); - if (count == 0) { - try { - jetty.start(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - } - } - - public static void stop() { - synchronized (jetty) { - int count = latch.getAndDecrement(); - if (count == 0) { - try { - jetty.stop(); - } catch (Exception e) { - throw new IllegalStateException(e); - } + try { + jetty.start(); // jetty will safely no-op a start on an already running instance + } catch (Exception e) { + throw new IllegalStateException(e); } } } diff --git a/src/test/java/org/jsoup/nodes/FormElementTest.java b/src/test/java/org/jsoup/nodes/FormElementTest.java index c64cbbfd20..ee7702ee21 100644 --- a/src/test/java/org/jsoup/nodes/FormElementTest.java +++ b/src/test/java/org/jsoup/nodes/FormElementTest.java @@ -7,7 +7,6 @@ import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.FileServlet; import org.jsoup.select.Elements; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -27,11 +26,6 @@ public static void setUp() { TestServer.start(); } - @AfterAll - public static void tearDown() { - TestServer.stop(); - } - @Test public void hasAssociatedControls() { //"button", "fieldset", "input", "keygen", "object", "output", "select", "textarea" String html = "<form id=1><button id=1><fieldset id=2 /><input id=3><keygen id=4><object id=5><output id=6>" + diff --git a/src/test/java/org/jsoup/nodes/PositionTest.java b/src/test/java/org/jsoup/nodes/PositionTest.java index e47817c8c5..813ed9b39f 100644 --- a/src/test/java/org/jsoup/nodes/PositionTest.java +++ b/src/test/java/org/jsoup/nodes/PositionTest.java @@ -1,12 +1,8 @@ package org.jsoup.nodes; import org.jsoup.Jsoup; -import org.jsoup.integration.TestServer; -import org.jsoup.integration.servlets.EchoServlet; import org.jsoup.integration.servlets.FileServlet; import org.jsoup.parser.Parser; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -145,16 +141,6 @@ class PositionTest { assertEquals("6,1:80-6,17:96", comment.sourceRange().toString()); } - @BeforeAll - static void setUp() { - TestServer.start(); - } - - @AfterAll - static void tearDown() { - TestServer.stop(); - } - @Test void tracksFromFetch() throws IOException { String url = FileServlet.urlTo("/htmltests/large.html"); // 280 K Document doc = Jsoup.connect(url).parser(TrackingParser).get(); From 653da57a61dc6fcb5a94efb44f514239bdef8613 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 14 Aug 2022 13:11:19 +1000 Subject: [PATCH 769/774] Normalized API doc link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fdd93eab9..f2177eb0da 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ See [**jsoup.org**](https://jsoup.org/) for downloads and the full [API document [![Build Status](https://github.com/jhy/jsoup/workflows/Build/badge.svg)](https://github.com/jhy/jsoup/actions?query=workflow%3ABuild) ## Example -Fetch the [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/index.html?org/jsoup/select/Elements.html): +Fetch the [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/org/jsoup/select/Elements.html): ```java Document doc = Jsoup.connect("https://en.wikipedia.org/").get(); From 6b67d05d883fdfe0f978d77d219e80324e0a223d Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 14 Aug 2022 19:01:47 +1000 Subject: [PATCH 770/774] Improved Validate error messages Filter the Validate class out of ValidationExceptions, to make it simpler to identify where the validation error originated. Made malformed URL and empty selection error messages more explicit. Simplified raising errors for null and empty parameters. --- CHANGES | 5 ++ .../java/org/jsoup/helper/HttpConnection.java | 64 +++++++------- src/main/java/org/jsoup/helper/Validate.java | 84 ++++++++++++++----- .../org/jsoup/helper/ValidationException.java | 34 ++++++++ src/main/java/org/jsoup/helper/W3CDom.java | 4 +- src/main/java/org/jsoup/nodes/Element.java | 10 ++- .../java/org/jsoup/parser/TreeBuilder.java | 4 +- .../java/org/jsoup/select/QueryParser.java | 2 +- .../org/jsoup/helper/HttpConnectionTest.java | 19 ++++- .../java/org/jsoup/helper/ValidateTest.java | 29 ++++++- .../java/org/jsoup/nodes/ElementTest.java | 27 ++++++ 11 files changed, 218 insertions(+), 64 deletions(-) create mode 100644 src/main/java/org/jsoup/helper/ValidationException.java diff --git a/CHANGES b/CHANGES index 63a5861dd3..d234fc607a 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,11 @@ Release 1.15.3 [PENDING] * Improvement: the Cleaner will preserve the source position of cleaned elements, if source tracking is enabled in the original parse. + * Improvement: the error messages output from Validate are more descriptive. Exceptions are now ValidationExceptions + (extending IllegalArgumentException). Stack traces do not include the Validate class, to make it simpler to see + where the exception originated. Common validation errors including malformed URLs and empty selector results have + more explicit error messages. + * Bugfix: the DataUtil would incorrectly read from InputStreams that emitted reads less than the requested size. This lead to incorrect results when parsing from chunked server responses, for e.g. <https://github.com/jhy/jsoup/issues/1807> diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index b99807302d..9cc6bc1414 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -179,11 +179,11 @@ public Connection url(URL url) { } public Connection url(String url) { - Validate.notEmpty(url, "Must supply a valid URL"); + Validate.notEmptyParam(url, "url"); try { req.url(new URL(encodeUrl(url))); } catch (MalformedURLException e) { - throw new IllegalArgumentException("Malformed URL: " + url, e); + throw new IllegalArgumentException(String.format("The supplied URL, '%s', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'.", url), e); } return this; } @@ -199,7 +199,7 @@ public Connection proxy(String host, int port) { } public Connection userAgent(String userAgent) { - Validate.notNull(userAgent, "User agent must not be null"); + Validate.notNullParam(userAgent, "userAgent"); req.header(USER_AGENT, userAgent); return this; } @@ -220,7 +220,7 @@ public Connection followRedirects(boolean followRedirects) { } public Connection referrer(String referrer) { - Validate.notNull(referrer, "Referrer must not be null"); + Validate.notNullParam(referrer, "referrer"); req.header("Referer", referrer); return this; } @@ -263,7 +263,7 @@ public Connection data(String key, String filename, InputStream inputStream, Str } public Connection data(Map<String, String> data) { - Validate.notNull(data, "Data map must not be null"); + Validate.notNullParam(data, "data"); for (Map.Entry<String, String> entry : data.entrySet()) { req.data(KeyVal.create(entry.getKey(), entry.getValue())); } @@ -271,7 +271,7 @@ public Connection data(Map<String, String> data) { } public Connection data(String... keyvals) { - Validate.notNull(keyvals, "Data key value pairs must not be null"); + Validate.notNullParam(keyvals, "keyvals"); Validate.isTrue(keyvals.length %2 == 0, "Must supply an even number of key value pairs"); for (int i = 0; i < keyvals.length; i += 2) { String key = keyvals[i]; @@ -284,7 +284,7 @@ public Connection data(String... keyvals) { } public Connection data(Collection<Connection.KeyVal> data) { - Validate.notNull(data, "Data collection must not be null"); + Validate.notNullParam(data, "data"); for (Connection.KeyVal entry: data) { req.data(entry); } @@ -292,7 +292,7 @@ public Connection data(Collection<Connection.KeyVal> data) { } public Connection.KeyVal data(String key) { - Validate.notEmpty(key, "Data key must not be empty"); + Validate.notEmptyParam(key, "key"); for (Connection.KeyVal keyVal : request().data()) { if (keyVal.key().equals(key)) return keyVal; @@ -311,7 +311,7 @@ public Connection header(String name, String value) { } public Connection headers(Map<String,String> headers) { - Validate.notNull(headers, "Header map must not be null"); + Validate.notNullParam(headers, "headers"); for (Map.Entry<String,String> entry : headers.entrySet()) { req.header(entry.getKey(),entry.getValue()); } @@ -324,7 +324,7 @@ public Connection cookie(String name, String value) { } public Connection cookies(Map<String, String> cookies) { - Validate.notNull(cookies, "Cookie map must not be null"); + Validate.notNullParam(cookies, "cookies"); for (Map.Entry<String, String> entry : cookies.entrySet()) { req.cookie(entry.getKey(), entry.getValue()); } @@ -432,7 +432,7 @@ public URL url() { } public T url(URL url) { - Validate.notNull(url, "URL must not be null"); + Validate.notNullParam(url, "url"); this.url = punyUrl(url); // if calling url(url) directly, does not go through encodeUrl, so we punycode it explicitly. todo - should we encode here as well? return (T) this; } @@ -442,13 +442,13 @@ public Method method() { } public T method(Method method) { - Validate.notNull(method, "Method must not be null"); + Validate.notNullParam(method, "method"); this.method = method; return (T) this; } public String header(String name) { - Validate.notNull(name, "Header name must not be null"); + Validate.notNullParam(name, "name"); List<String> vals = getHeadersCaseInsensitive(name); if (vals.size() > 0) { // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 @@ -460,7 +460,7 @@ public String header(String name) { @Override public T addHeader(String name, String value) { - Validate.notEmpty(name); + Validate.notEmptyParam(name, "name"); //noinspection ConstantConditions value = value == null ? "" : value; @@ -476,7 +476,7 @@ public T addHeader(String name, String value) { @Override public List<String> headers(String name) { - Validate.notEmpty(name); + Validate.notEmptyParam(name, "name"); return getHeadersCaseInsensitive(name); } @@ -530,14 +530,14 @@ private static boolean looksLikeUtf8(byte[] input) { } public T header(String name, String value) { - Validate.notEmpty(name, "Header name must not be empty"); + Validate.notEmptyParam(name, "name"); removeHeader(name); // ensures we don't get an "accept-encoding" and a "Accept-Encoding" addHeader(name, value); return (T) this; } public boolean hasHeader(String name) { - Validate.notEmpty(name, "Header name must not be empty"); + Validate.notEmptyParam(name, "name"); return !getHeadersCaseInsensitive(name).isEmpty(); } @@ -556,8 +556,8 @@ public boolean hasHeaderWithValue(String name, String value) { } public T removeHeader(String name) { - Validate.notEmpty(name, "Header name must not be empty"); - Map.Entry<String, List<String>> entry = scanHeaders(name); // remove is case insensitive too + Validate.notEmptyParam(name, "name"); + Map.Entry<String, List<String>> entry = scanHeaders(name); // remove is case-insensitive too if (entry != null) headers.remove(entry.getKey()); // ensures correct case return (T) this; @@ -600,24 +600,24 @@ private List<String> getHeadersCaseInsensitive(String name) { } public String cookie(String name) { - Validate.notEmpty(name, "Cookie name must not be empty"); + Validate.notEmptyParam(name, "name"); return cookies.get(name); } public T cookie(String name, String value) { - Validate.notEmpty(name, "Cookie name must not be empty"); - Validate.notNull(value, "Cookie value must not be null"); + Validate.notEmptyParam(name, "name"); + Validate.notNullParam(value, "value"); cookies.put(name, value); return (T) this; } public boolean hasCookie(String name) { - Validate.notEmpty(name, "Cookie name must not be empty"); + Validate.notEmptyParam(name, "name"); return cookies.containsKey(name); } public T removeCookie(String name) { - Validate.notEmpty(name, "Cookie name must not be empty"); + Validate.notEmptyParam(name, "name"); cookies.remove(name); return (T) this; } @@ -749,7 +749,7 @@ public Connection.Request ignoreContentType(boolean ignoreContentType) { } public Request data(Connection.KeyVal keyval) { - Validate.notNull(keyval, "Key val must not be null"); + Validate.notNullParam(keyval, "keyval"); data.add(keyval); return this; } @@ -778,7 +778,7 @@ public Parser parser() { } public Connection.Request postDataCharset(String charset) { - Validate.notNull(charset, "Charset must not be null"); + Validate.notNullParam(charset, "charset"); if (!Charset.isSupported(charset)) throw new IllegalCharsetNameException(charset); this.postDataCharset = charset; return this; @@ -834,7 +834,7 @@ static Response execute(HttpConnection.Request req, @Nullable Response previousR Validate.isFalse(req.executing, "Multiple threads were detected trying to execute the same request concurrently. Make sure to use Connection#newRequest() and do not share an executing request between threads."); req.executing = true; } - Validate.notNull(req, "Request must not be null"); + Validate.notNullParam(req, "req"); URL url = req.url(); Validate.notNull(url, "URL must be specified to connect"); String protocol = url.getProtocol(); @@ -1275,14 +1275,14 @@ public static KeyVal create(String key, String filename, InputStream stream) { } private KeyVal(String key, String value) { - Validate.notEmpty(key, "Data key must not be empty"); - Validate.notNull(value, "Data value must not be null"); + Validate.notEmptyParam(key, "key"); + Validate.notNullParam(value, "value"); this.key = key; this.value = value; } public KeyVal key(String key) { - Validate.notEmpty(key, "Data key must not be empty"); + Validate.notEmptyParam(key, "key"); this.key = key; return this; } @@ -1292,7 +1292,7 @@ public String key() { } public KeyVal value(String value) { - Validate.notNull(value, "Data value must not be null"); + Validate.notNullParam(value, "value"); this.value = value; return this; } @@ -1302,7 +1302,7 @@ public String value() { } public KeyVal inputStream(InputStream inputStream) { - Validate.notNull(value, "Data input stream must not be null"); + Validate.notNullParam(value, "inputStream"); this.stream = inputStream; return this; } diff --git a/src/main/java/org/jsoup/helper/Validate.java b/src/main/java/org/jsoup/helper/Validate.java index 3aaa3c802e..656c9114be 100644 --- a/src/main/java/org/jsoup/helper/Validate.java +++ b/src/main/java/org/jsoup/helper/Validate.java @@ -3,7 +3,7 @@ import javax.annotation.Nullable; /** - * Simple validation methods. Designed for jsoup internal use. + * Validators to check that method arguments meet expectations. */ public final class Validate { @@ -12,22 +12,34 @@ private Validate() {} /** * Validates that the object is not null * @param obj object to test - * @throws IllegalArgumentException if the object is null + * @throws ValidationException if the object is null */ public static void notNull(@Nullable Object obj) { if (obj == null) - throw new IllegalArgumentException("Object must not be null"); + throw new ValidationException("Object must not be null"); + } + + /** + Validates that the parameter is not null + + * @param obj the parameter to test + * @param param the name of the parameter, for presentation in the validation exception. + * @throws ValidationException if the object is null + */ + public static void notNullParam(@Nullable final Object obj, final String param) { + if (obj == null) + throw new ValidationException(String.format("The parameter '%s' must not be null.", param)); } /** * Validates that the object is not null * @param obj object to test * @param msg message to include in the Exception if validation fails - * @throws IllegalArgumentException if the object is null + * @throws ValidationException if the object is null */ public static void notNull(@Nullable Object obj, String msg) { if (obj == null) - throw new IllegalArgumentException(msg); + throw new ValidationException(msg); } /** @@ -35,60 +47,75 @@ public static void notNull(@Nullable Object obj, String msg) { null object. (Works around lack of Objects.requestNonNull in Android version.) * @param obj nullable object to case to not-null * @return the object, or throws an exception if it is null - * @throws IllegalArgumentException if the object is null + * @throws ValidationException if the object is null */ public static Object ensureNotNull(@Nullable Object obj) { if (obj == null) - throw new IllegalArgumentException("Object must not be null"); + throw new ValidationException("Object must not be null"); + else return obj; + } + + /** + Verifies the input object is not null, and returns that object. Effectively this casts a nullable object to a non- + null object. (Works around lack of Objects.requestNonNull in Android version.) + * @param obj nullable object to case to not-null + * @param msg the String format message to include in the validation exception when thrown + * @param args the arguments to the msg + * @return the object, or throws an exception if it is null + * @throws ValidationException if the object is null + */ + public static Object ensureNotNull(@Nullable Object obj, String msg, Object... args) { + if (obj == null) + throw new ValidationException(String.format(msg, args)); else return obj; } /** * Validates that the value is true * @param val object to test - * @throws IllegalArgumentException if the object is not true + * @throws ValidationException if the object is not true */ public static void isTrue(boolean val) { if (!val) - throw new IllegalArgumentException("Must be true"); + throw new ValidationException("Must be true"); } /** * Validates that the value is true * @param val object to test * @param msg message to include in the Exception if validation fails - * @throws IllegalArgumentException if the object is not true + * @throws ValidationException if the object is not true */ public static void isTrue(boolean val, String msg) { if (!val) - throw new IllegalArgumentException(msg); + throw new ValidationException(msg); } /** * Validates that the value is false * @param val object to test - * @throws IllegalArgumentException if the object is not false + * @throws ValidationException if the object is not false */ public static void isFalse(boolean val) { if (val) - throw new IllegalArgumentException("Must be false"); + throw new ValidationException("Must be false"); } /** * Validates that the value is false * @param val object to test * @param msg message to include in the Exception if validation fails - * @throws IllegalArgumentException if the object is not false + * @throws ValidationException if the object is not false */ public static void isFalse(boolean val, String msg) { if (val) - throw new IllegalArgumentException(msg); + throw new ValidationException(msg); } /** * Validates that the array contains no null elements * @param objects the array to test - * @throws IllegalArgumentException if the array contains a null element + * @throws ValidationException if the array contains a null element */ public static void noNullElements(Object[] objects) { noNullElements(objects, "Array must not contain any null objects"); @@ -98,33 +125,44 @@ public static void noNullElements(Object[] objects) { * Validates that the array contains no null elements * @param objects the array to test * @param msg message to include in the Exception if validation fails - * @throws IllegalArgumentException if the array contains a null element + * @throws ValidationException if the array contains a null element */ public static void noNullElements(Object[] objects, String msg) { for (Object obj : objects) if (obj == null) - throw new IllegalArgumentException(msg); + throw new ValidationException(msg); } /** * Validates that the string is not null and is not empty * @param string the string to test - * @throws IllegalArgumentException if the string is null or empty + * @throws ValidationException if the string is null or empty */ public static void notEmpty(@Nullable String string) { if (string == null || string.length() == 0) - throw new IllegalArgumentException("String must not be empty"); + throw new ValidationException("String must not be empty"); + } + + /** + Validates that the string parameter is not null and is not empty + * @param string the string to test + * @param param the name of the parameter, for presentation in the validation exception. + * @throws ValidationException if the string is null or empty + */ + public static void notEmptyParam(@Nullable final String string, final String param) { + if (string == null || string.length() == 0) + throw new ValidationException(String.format("The '%s' parameter must not be empty.", param)); } /** * Validates that the string is not null and is not empty * @param string the string to test * @param msg message to include in the Exception if validation fails - * @throws IllegalArgumentException if the string is null or empty + * @throws ValidationException if the string is null or empty */ public static void notEmpty(@Nullable String string, String msg) { if (string == null || string.length() == 0) - throw new IllegalArgumentException(msg); + throw new ValidationException(msg); } /** @@ -142,6 +180,6 @@ public static void wtf(String msg) { @throws IllegalStateException if we reach this state */ public static void fail(String msg) { - throw new IllegalArgumentException(msg); + throw new ValidationException(msg); } } diff --git a/src/main/java/org/jsoup/helper/ValidationException.java b/src/main/java/org/jsoup/helper/ValidationException.java new file mode 100644 index 0000000000..916cf12889 --- /dev/null +++ b/src/main/java/org/jsoup/helper/ValidationException.java @@ -0,0 +1,34 @@ +package org.jsoup.helper; + +import java.util.ArrayList; +import java.util.List; + +/** + Validation exceptions, as thrown by the methods in {@link Validate}. + */ +public class ValidationException extends IllegalArgumentException { + + public static final String Validator = Validate.class.getName(); + + public ValidationException(String msg) { + super(msg); + } + + @Override + public synchronized Throwable fillInStackTrace() { + // Filters out the Validate class from the stacktrace, to more clearly point at the root-cause. + + super.fillInStackTrace(); + + StackTraceElement[] stackTrace = getStackTrace(); + List<StackTraceElement> filteredTrace = new ArrayList<>(); + for (StackTraceElement trace : stackTrace) { + if (trace.getClassName().equals(Validator)) continue; + filteredTrace.add(trace); + } + + setStackTrace(filteredTrace.toArray(new StackTraceElement[0])); + + return this; + } +} diff --git a/src/main/java/org/jsoup/helper/W3CDom.java b/src/main/java/org/jsoup/helper/W3CDom.java index f300b400c1..8caf31f525 100644 --- a/src/main/java/org/jsoup/helper/W3CDom.java +++ b/src/main/java/org/jsoup/helper/W3CDom.java @@ -269,8 +269,8 @@ public NodeList selectXpath(String xpath, Document doc) { @return the matches nodes */ public NodeList selectXpath(String xpath, Node contextNode) { - Validate.notEmpty(xpath); - Validate.notNull(contextNode); + Validate.notEmptyParam(xpath, "xpath"); + Validate.notNullParam(contextNode, "contextNode"); NodeList nodeList; try { diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 016dac13bd..eed137ac42 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -172,7 +172,7 @@ public String normalName() { * @see Elements#tagName(String) */ public Element tagName(String tagName) { - Validate.notEmpty(tagName, "Tag name must not be empty."); + Validate.notEmptyParam(tagName, "tagName"); tag = Tag.valueOf(tagName, NodeUtils.parser(this).settings()); // maintains the case option of the original parse return this; } @@ -468,7 +468,13 @@ public Elements select(Evaluator evaluator) { @since 1.15.2 */ public Element expectFirst(String cssQuery) { - return (Element) Validate.ensureNotNull(Selector.selectFirst(cssQuery, this)); + return (Element) Validate.ensureNotNull( + Selector.selectFirst(cssQuery, this), + parent() != null ? + "No elements matched the query '%s' on element '%s'.": + "No elements matched the query '%s' in the document." + , cssQuery, this.tagName() + ); } /** diff --git a/src/main/java/org/jsoup/parser/TreeBuilder.java b/src/main/java/org/jsoup/parser/TreeBuilder.java index 902cbdf0ae..77083b2ea0 100644 --- a/src/main/java/org/jsoup/parser/TreeBuilder.java +++ b/src/main/java/org/jsoup/parser/TreeBuilder.java @@ -37,8 +37,8 @@ abstract class TreeBuilder { @ParametersAreNonnullByDefault protected void initialiseParse(Reader input, String baseUri, Parser parser) { - Validate.notNull(input, "String input must not be null"); - Validate.notNull(baseUri, "BaseURI must not be null"); + Validate.notNullParam(input, "input"); + Validate.notNullParam(baseUri, "baseUri"); Validate.notNull(parser); doc = new Document(baseUri); diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java index 3b2b735696..bed380db7b 100644 --- a/src/main/java/org/jsoup/select/QueryParser.java +++ b/src/main/java/org/jsoup/select/QueryParser.java @@ -360,7 +360,7 @@ private int consumeIndex() { private void has() { tq.consume(":has"); String subQuery = tq.chompBalanced('(', ')'); - Validate.notEmpty(subQuery, ":has(selector) subselect must not be empty"); + Validate.notEmpty(subQuery, ":has(selector) sub-select must not be empty"); evals.add(new StructuralEvaluator.Has(parse(subQuery))); } diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 3ed9cf9f6c..4bd7dfb502 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -9,7 +9,13 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -293,4 +299,15 @@ public void caseInsensitiveHeaders(Locale locale) { } assertTrue(urlThrew); } + + @Test void testMalformedException() { + boolean threw = false; + try { + Jsoup.connect("jsoup.org/test"); + } catch (IllegalArgumentException e) { + threw = true; + assertEquals("The supplied URL, 'jsoup.org/test', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'.", e.getMessage()); + } + assertTrue(threw); + } } diff --git a/src/test/java/org/jsoup/helper/ValidateTest.java b/src/test/java/org/jsoup/helper/ValidateTest.java index 138e532420..c7a093c358 100644 --- a/src/test/java/org/jsoup/helper/ValidateTest.java +++ b/src/test/java/org/jsoup/helper/ValidateTest.java @@ -3,8 +3,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class ValidateTest { +import static org.junit.jupiter.api.Assertions.*; +public class ValidateTest { @Test public void testNotNull() { Validate.notNull("foo"); @@ -16,4 +17,30 @@ public void testNotNull() { } Assertions.assertTrue(threw); } + + @Test void stacktraceFiltersOutValidateClass() { + boolean threw = false; + try { + Validate.notNull(null); + } catch (ValidationException e) { + threw = true; + assertEquals("Object must not be null", e.getMessage()); + StackTraceElement[] stackTrace = e.getStackTrace(); + for (StackTraceElement trace : stackTrace) { + assertNotEquals(trace.getClassName(), Validate.class.getName()); + } + assertTrue(stackTrace.length >= 1); + } + Assertions.assertTrue(threw); + } + + @Test void nonnullParam() { + boolean threw = true; + try { + Validate.notNullParam(null, "foo"); + } catch (ValidationException e) { + assertEquals("The parameter 'foo' must not be null.", e.getMessage()); + } + assertTrue(threw); + } } diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java index 1f92eb11d1..0f51dc3ff8 100644 --- a/src/test/java/org/jsoup/nodes/ElementTest.java +++ b/src/test/java/org/jsoup/nodes/ElementTest.java @@ -2,6 +2,7 @@ import org.jsoup.Jsoup; import org.jsoup.TextUtil; +import org.jsoup.helper.ValidationException; import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; @@ -2272,6 +2273,32 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) { assertTrue(threw); } + @Test void testExpectFirstMessage() { + Document doc = Jsoup.parse("<p>One</p><p>Two <span>Three</span> <span>Four</span>"); + boolean threw = false; + Element p = doc.expectFirst("P"); + try { + Element span = p.expectFirst("span.doesNotExist"); + } catch (ValidationException e) { + threw = true; + assertEquals("No elements matched the query 'span.doesNotExist' on element 'p'.", e.getMessage()); + } + assertTrue(threw); + } + + @Test void testExpectFirstMessageDoc() { + Document doc = Jsoup.parse("<p>One</p><p>Two <span>Three</span> <span>Four</span>"); + boolean threw = false; + Element p = doc.expectFirst("P"); + try { + Element span = doc.expectFirst("span.doesNotExist"); + } catch (ValidationException e) { + threw = true; + assertEquals("No elements matched the query 'span.doesNotExist' in the document.", e.getMessage()); + } + assertTrue(threw); + } + @Test void spanRunsMaintainSpace() { // https://github.com/jhy/jsoup/issues/1787 Document doc = Jsoup.parse("<p><span>One</span>\n<span>Two</span>\n<span>Three</span></p>"); From 985f1fe13aa0b1d37c6f6f17a948fc240a03dcef Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 14 Aug 2022 19:08:50 +1000 Subject: [PATCH 771/774] Include help link for malformed URLs Try to hint people to use absUrl() --- src/main/java/org/jsoup/helper/HttpConnection.java | 2 +- src/test/java/org/jsoup/helper/HttpConnectionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 9cc6bc1414..6b856fe68e 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -183,7 +183,7 @@ public Connection url(String url) { try { req.url(new URL(encodeUrl(url))); } catch (MalformedURLException e) { - throw new IllegalArgumentException(String.format("The supplied URL, '%s', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'.", url), e); + throw new IllegalArgumentException(String.format("The supplied URL, '%s', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'. See https://jsoup.org/cookbook/extracting-data/working-with-urls", url), e); } return this; } diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 4bd7dfb502..1fe5181509 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -306,7 +306,7 @@ public void caseInsensitiveHeaders(Locale locale) { Jsoup.connect("jsoup.org/test"); } catch (IllegalArgumentException e) { threw = true; - assertEquals("The supplied URL, 'jsoup.org/test', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'.", e.getMessage()); + assertEquals("The supplied URL, 'jsoup.org/test', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'. See https://jsoup.org/cookbook/extracting-data/working-with-urls", e.getMessage()); } assertTrue(threw); } From 4ea768d96b3d232e63edef9594766d44597b3882 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Sun, 21 Aug 2022 14:04:56 +1000 Subject: [PATCH 772/774] Strip control characters from URLs when resolving absolute URLs --- .../java/org/jsoup/internal/StringUtil.java | 10 +++++++++- .../org/jsoup/internal/StringUtilTest.java | 9 +++++++++ .../java/org/jsoup/safety/CleanerTest.java | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index 8c5e50b87f..73a589b17c 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -290,6 +290,7 @@ public static boolean isAscii(String string) { * @throws MalformedURLException if an error occurred generating the URL */ public static URL resolve(URL base, String relUrl) throws MalformedURLException { + relUrl = stripControlChars(relUrl); // workaround: java resolves '//path/file + ?foo' to '//path/?foo', not '//path/file?foo' as desired if (relUrl.startsWith("?")) relUrl = base.getPath() + relUrl; @@ -308,7 +309,9 @@ public static URL resolve(URL base, String relUrl) throws MalformedURLException * @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned) * @return an absolute URL if one was able to be generated, or the empty string if not */ - public static String resolve(final String baseUrl, final String relUrl) { + public static String resolve(String baseUrl, String relUrl) { + // workaround: java will allow control chars in a path URL and may treat as relative, but Chrome / Firefox will strip and may see as a scheme. Normalize to browser's view. + baseUrl = stripControlChars(baseUrl); relUrl = stripControlChars(relUrl); try { URL base; try { @@ -327,6 +330,11 @@ public static String resolve(final String baseUrl, final String relUrl) { } private static final Pattern validUriScheme = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+-.]*:"); + private static final Pattern controlChars = Pattern.compile("[\\x00-\\x1f]*"); // matches ascii 0 - 31, to strip from url + private static String stripControlChars(final String input) { + return controlChars.matcher(input).replaceAll(""); + } + private static final ThreadLocal<Stack<StringBuilder>> threadLocalBuilders = new ThreadLocal<Stack<StringBuilder>>() { @Override protected Stack<StringBuilder> initialValue() { diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index 2f4fff5da0..84cca12dbc 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -147,6 +147,15 @@ public void join() { assertEquals("http://example.com/b/c/g#s/../x", resolve("http://example.com/b/c/d;p?q", "g#s/../x")); } + @Test void stripsControlCharsFromUrls() { + // should resovle to an absolute url: + assertEquals("foo:bar", resolve("\nhttps://\texample.com/", "\r\nfo\to:ba\br")); + } + + @Test void allowsSpaceInUrl() { + assertEquals("https://example.com/foo bar/", resolve("HTTPS://example.com/example/", "../foo bar/")); + } + @Test void isAscii() { assertTrue(StringUtil.isAscii("")); diff --git a/src/test/java/org/jsoup/safety/CleanerTest.java b/src/test/java/org/jsoup/safety/CleanerTest.java index 3fc6dbba05..8a03ed02dc 100644 --- a/src/test/java/org/jsoup/safety/CleanerTest.java +++ b/src/test/java/org/jsoup/safety/CleanerTest.java @@ -213,6 +213,24 @@ public void safeListedProtocolShouldBeRetained(Locale locale) { assertEquals("<a rel=\"nofollow\">Link</a>", clean); } + @Test void dropsConcealedJavascriptProtocolWhenRelativesLinksEnabled() { + Safelist safelist = Safelist.basic().preserveRelativeLinks(true); + String html = "<a href=\"&#0013;ja&Tab;va&Tab;script&#0010;:alert(1)\">Link</a>"; + String clean = Jsoup.clean(html, "https://", safelist); + assertEquals("<a rel=\"nofollow\">Link</a>", clean); + + String colon = "<a href=\"ja&Tab;va&Tab;script&colon;alert(1)\">Link</a>"; + String cleanColon = Jsoup.clean(colon, "https://", safelist); + assertEquals("<a rel=\"nofollow\">Link</a>", cleanColon); + } + + @Test void dropsConcealedJavascriptProtocolWhenRelativesLinksDisabled() { + Safelist safelist = Safelist.basic().preserveRelativeLinks(false); + String html = "<a href=\"ja&Tab;vas&#0013;cript:alert(1)\">Link</a>"; + String clean = Jsoup.clean(html, "https://", safelist); + assertEquals("<a rel=\"nofollow\">Link</a>", clean); + } + @Test public void handlesCustomProtocols() { String html = "<img src='cid:12345' /> <img src='data:gzzt' />"; String dropped = Jsoup.clean(html, Safelist.basicWithImages()); From d2d9ac341dbd48e75c6dd09c571216a81939604f Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 24 Aug 2022 10:08:31 +1000 Subject: [PATCH 773/774] Changelog for URL cleaner improvement SHA - 4ea768d96b3d232e63edef9594766d44597b3882 --- CHANGES | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d234fc607a..2f871871d8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ jsoup changelog -Release 1.15.3 [PENDING] +Release 1.15.3 [2022-Aug-24] + * Security: fixed an issue where the jsoup cleaner may incorrectly sanitize crafted XSS attempts if + SafeList.preserveRelativeLinks is enabled. + <https://github.com/jhy/jsoup/security/advisories/GHSA-gp7f-rwcx-9369> + * Improvement: the Cleaner will preserve the source position of cleaned elements, if source tracking is enabled in the original parse. From c5964172763e1495786ad584c368ac3346d0ca8c Mon Sep 17 00:00:00 2001 From: Jonathan Hedley <jonathan@hedley.net> Date: Wed, 24 Aug 2022 10:10:43 +1000 Subject: [PATCH 774/774] [maven-release-plugin] prepare release jsoup-1.15.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9650d72c0c..b78cd3a79b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> - <version>1.15.3-SNAPSHOT</version><!-- remember to update previous version below for japicmp --> + <version>1.15.3</version><!-- remember to update previous version below for japicmp --> <description>jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers do.</description> <url>https://jsoup.org/</url> <inceptionYear>2009</inceptionYear> @@ -24,7 +24,7 @@ <url>https://github.com/jhy/jsoup</url> <connection>scm:git:https://github.com/jhy/jsoup.git</connection> <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> --> - <tag>HEAD</tag> + <tag>jsoup-1.15.3</tag> </scm> <organization> <name>Jonathan Hedley</name>